1/*
2 * Copyright (C) 2004-2008 Justin Karneges <justin@affinix.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
18 *
19 */
20
21// Note: The basic thread-safety approach with the plugin manager is that
22// it is safe to add/get providers, however it is unsafe to remove them.
23// The expectation is that providers will almost always be unloaded on
24// application shutdown. For safe provider unload, ensure no threads are
25// using the manager, the provider in question, nor any sub-objects from
26// the provider.
27
28#include "qca_plugin.h"
29
30#include "qcaprovider.h"
31
32#include <QCoreApplication>
33#include <QDir>
34#include <QFileInfo>
35#include <QLibrary>
36#include <QPluginLoader>
37
38#define PLUGIN_SUBDIR QStringLiteral("crypto")
39
40namespace QCA {
41
42// from qca_core.cpp
43QVariantMap getProviderConfig_internal(Provider *p);
44
45// from qca_default.cpp
46QStringList skip_plugins(Provider *defaultProvider);
47QStringList plugin_priorities(Provider *defaultProvider);
48
49// stupidly simple log truncation function. if the log exceeds size chars,
50// then throw out the top half, to nearest line.
51QString truncate_log(const QString &in, int size)
52{
53 if (size < 2 || in.length() < size)
54 return in;
55
56 // start by pointing at the last chars
57 int at = in.length() - (size / 2);
58
59 // if the previous char is a newline, then this is a perfect cut.
60 // otherwise, we need to skip to after the next newline.
61 if (in[at - 1] != QLatin1Char('\n')) {
62 while (at < in.length() && in[at] != QLatin1Char('\n')) {
63 ++at;
64 }
65
66 // at this point we either reached a newline, or end of
67 // the entire buffer
68
69 if (in[at] == QLatin1Char('\n'))
70 ++at;
71 }
72
73 return in.mid(position: at);
74}
75
76static ProviderManager *g_pluginman = nullptr;
77
78static void logDebug(const QString &str)
79{
80 if (g_pluginman)
81 g_pluginman->appendDiagnosticText(str: str + QLatin1Char('\n'));
82}
83
84static bool validVersion(int ver)
85{
86 // major version must be equal, minor version must be equal or lesser
87 if ((ver & 0xff0000) == (QCA_VERSION & 0xff0000) && (ver & 0xff00) <= (QCA_VERSION & 0xff00))
88 return true;
89 return false;
90}
91
92class PluginInstance
93{
94private:
95 QPluginLoader *_loader;
96 QObject *_instance;
97 bool _ownInstance;
98
99 PluginInstance()
100 {
101 }
102
103public:
104 static PluginInstance *fromFile(const QString &fname, QString *errstr = nullptr)
105 {
106 QPluginLoader *loader = new QPluginLoader(fname);
107 if (!loader->load()) {
108 if (errstr)
109 *errstr = QStringLiteral("failed to load: %1").arg(a: loader->errorString());
110 delete loader;
111 return nullptr;
112 }
113 QObject *obj = loader->instance();
114 if (!obj) {
115 if (errstr)
116 *errstr = QStringLiteral("failed to get instance");
117 loader->unload();
118 delete loader;
119 return nullptr;
120 }
121 PluginInstance *i = new PluginInstance;
122 i->_loader = loader;
123 i->_instance = obj;
124 i->_ownInstance = true;
125 return i;
126 }
127
128 static PluginInstance *fromStatic(QObject *obj)
129 {
130 PluginInstance *i = new PluginInstance;
131 i->_loader = nullptr;
132 i->_instance = obj;
133 i->_ownInstance = false;
134 return i;
135 }
136
137 static PluginInstance *fromInstance(QObject *obj)
138 {
139 PluginInstance *i = new PluginInstance;
140 i->_loader = nullptr;
141 i->_instance = obj;
142 i->_ownInstance = true;
143 return i;
144 }
145
146 ~PluginInstance()
147 {
148 if (_ownInstance)
149 delete _instance;
150
151 if (_loader) {
152 _loader->unload();
153 delete _loader;
154 }
155 }
156
157 PluginInstance(const PluginInstance &) = delete;
158 PluginInstance &operator=(const PluginInstance &) = delete;
159
160 void claim()
161 {
162 if (_loader)
163 _loader->moveToThread(thread: nullptr);
164 if (_ownInstance)
165 _instance->moveToThread(thread: nullptr);
166 }
167
168 QObject *instance()
169 {
170 return _instance;
171 }
172};
173
174class ProviderItem
175{
176public:
177 QString fname;
178 Provider *p;
179 int priority;
180 QMutex m;
181
182 static ProviderItem *load(const QString &fname, QString *out_errstr = nullptr)
183 {
184 QString errstr;
185 PluginInstance *i = PluginInstance::fromFile(fname, errstr: &errstr);
186 if (!i) {
187 if (out_errstr)
188 *out_errstr = errstr;
189 return nullptr;
190 }
191 QCAPlugin *plugin = qobject_cast<QCAPlugin *>(object: i->instance());
192 if (!plugin) {
193 if (out_errstr)
194 *out_errstr = QStringLiteral("does not offer QCAPlugin interface");
195 delete i;
196 return nullptr;
197 }
198
199 Provider *p = plugin->createProvider();
200 if (!p) {
201 if (out_errstr)
202 *out_errstr = QStringLiteral("unable to create provider");
203 delete i;
204 return nullptr;
205 }
206
207 ProviderItem *pi = new ProviderItem(i, p);
208 pi->fname = fname;
209 return pi;
210 }
211
212 static ProviderItem *loadStatic(QObject *instance, QString *errstr = nullptr)
213 {
214 PluginInstance *i = PluginInstance::fromStatic(obj: instance);
215 QCAPlugin *plugin = qobject_cast<QCAPlugin *>(object: i->instance());
216 if (!plugin) {
217 if (errstr)
218 *errstr = QStringLiteral("does not offer QCAPlugin interface");
219 delete i;
220 return nullptr;
221 }
222
223 Provider *p = plugin->createProvider();
224 if (!p) {
225 if (errstr)
226 *errstr = QStringLiteral("unable to create provider");
227 delete i;
228 return nullptr;
229 }
230
231 ProviderItem *pi = new ProviderItem(i, p);
232 return pi;
233 }
234
235 static ProviderItem *fromClass(Provider *p)
236 {
237 ProviderItem *pi = new ProviderItem(nullptr, p);
238 return pi;
239 }
240
241 ~ProviderItem()
242 {
243 delete p;
244 delete instance;
245 }
246
247 void ensureInit()
248 {
249 QMutexLocker locker(&m);
250 if (init_done)
251 return;
252 init_done = true;
253
254 p->init();
255
256 // load config
257 QVariantMap conf = getProviderConfig_internal(p);
258 if (!conf.isEmpty())
259 p->configChanged(config: conf);
260 }
261
262 bool initted() const
263 {
264 return init_done;
265 }
266
267 // null if not a plugin
268 QObject *objectInstance() const
269 {
270 if (instance)
271 return instance->instance();
272 else
273 return nullptr;
274 }
275
276private:
277 PluginInstance *instance;
278 bool init_done;
279
280 ProviderItem(PluginInstance *_instance, Provider *_p)
281 {
282 instance = _instance;
283 p = _p;
284 init_done = false;
285
286 // disassociate from threads
287 if (instance)
288 instance->claim();
289 }
290};
291
292ProviderManager::ProviderManager()
293{
294 g_pluginman = this;
295 def = nullptr;
296 scanned_static = false;
297}
298
299ProviderManager::~ProviderManager()
300{
301 if (def)
302 def->deinit();
303 unloadAll();
304 delete def;
305 g_pluginman = nullptr;
306}
307
308void ProviderManager::scan()
309{
310 QMutexLocker locker(&providerMutex);
311
312 // check static first, but only once
313 if (!scanned_static) {
314 logDebug(QStringLiteral("Checking Qt static plugins:"));
315 const QObjectList list = QPluginLoader::staticInstances();
316 if (list.isEmpty())
317 logDebug(QStringLiteral(" (none)"));
318 for (int n = 0; n < list.count(); ++n) {
319 QObject *instance = list[n];
320 const QString className = QString::fromLatin1(ba: instance->metaObject()->className());
321
322 QString errstr;
323 ProviderItem *i = ProviderItem::loadStatic(instance, errstr: &errstr);
324 if (!i) {
325 logDebug(QStringLiteral(" %1: %2").arg(args: className, args&: errstr));
326 continue;
327 }
328
329 const QString providerName = i->p->name();
330 if (haveAlready(name: providerName)) {
331 logDebug(
332 QStringLiteral(" %1: (as %2) already loaded provider, skipping").arg(args: className, args: providerName));
333 delete i;
334 continue;
335 }
336
337 const int ver = i->p->qcaVersion();
338 if (!validVersion(ver)) {
339 errstr = QString::asprintf(format: "plugin version 0x%06x is in the future", ver);
340 logDebug(QStringLiteral(" %1: (as %2) %3").arg(args: className, args: providerName, args&: errstr));
341 delete i;
342 continue;
343 }
344
345 addItem(i, priority: get_default_priority(name: providerName));
346 logDebug(QStringLiteral(" %1: loaded as %2").arg(args: className, args: providerName));
347 }
348 scanned_static = true;
349 }
350
351#ifndef QCA_NO_PLUGINS
352 if (qgetenv(varName: "QCA_NO_PLUGINS") == "1")
353 return;
354
355 const QStringList dirs = pluginPaths();
356 if (dirs.isEmpty())
357 logDebug(QStringLiteral("No Qt Library Paths"));
358 for (const QString &dirIt : dirs) {
359#ifdef DEVELOPER_MODE
360 logDebug(QStringLiteral("Checking QCA build tree Path: %1").arg(QDir::toNativeSeparators(dirIt)));
361#else
362 logDebug(QStringLiteral("Checking Qt Library Path: %1").arg(a: QDir::toNativeSeparators(pathName: dirIt)));
363#endif
364 QDir libpath(dirIt);
365 QDir dir(libpath.filePath(PLUGIN_SUBDIR));
366 if (!dir.exists()) {
367 logDebug(QStringLiteral(" (No 'crypto' subdirectory)"));
368 continue;
369 }
370
371 const QStringList entryList = dir.entryList(filters: QDir::Files);
372 if (entryList.isEmpty()) {
373 logDebug(QStringLiteral(" (No files in 'crypto' subdirectory)"));
374 continue;
375 }
376
377 foreach (const QString &maybeFile, entryList) {
378 const QFileInfo fi(dir.filePath(fileName: maybeFile));
379
380 const QString filePath = fi.filePath(); // file name with path
381 const QString fileName = fi.fileName(); // just file name
382
383 if (!QLibrary::isLibrary(fileName: filePath)) {
384 logDebug(QStringLiteral(" %1: not a library, skipping").arg(a: fileName));
385 continue;
386 }
387
388 // make sure we haven't loaded this file before
389 bool haveFile = false;
390 for (int n = 0; n < providerItemList.count(); ++n) {
391 ProviderItem *pi = providerItemList[n];
392 if (!pi->fname.isEmpty() && pi->fname == filePath) {
393 haveFile = true;
394 break;
395 }
396 }
397 if (haveFile) {
398 logDebug(QStringLiteral(" %1: already loaded file, skipping").arg(a: fileName));
399 continue;
400 }
401
402 QString errstr;
403 ProviderItem *i = ProviderItem::load(fname: filePath, out_errstr: &errstr);
404 if (!i) {
405 logDebug(QStringLiteral(" %1: %2").arg(args: fileName, args&: errstr));
406 continue;
407 }
408
409 const QString className = QString::fromLatin1(ba: i->objectInstance()->metaObject()->className());
410
411 const QString providerName = i->p->name();
412 if (haveAlready(name: providerName)) {
413 logDebug(QStringLiteral(" %1: (class: %2, as %3) already loaded provider, skipping")
414 .arg(args: fileName, args: className, args: providerName));
415 delete i;
416 continue;
417 }
418
419 const int ver = i->p->qcaVersion();
420 if (!validVersion(ver)) {
421 errstr = QString::asprintf(format: "plugin version 0x%06x is in the future", ver);
422 logDebug(QStringLiteral(" %1: (class: %2, as %3) %4").arg(args: fileName, args: className, args: providerName, args&: errstr));
423 delete i;
424 continue;
425 }
426
427 if (skip_plugins(defaultProvider: def).contains(str: providerName)) {
428 logDebug(QStringLiteral(" %1: (class: %2, as %3) explicitly disabled, skipping")
429 .arg(args: fileName, args: className, args: providerName));
430 delete i;
431 continue;
432 }
433
434 addItem(i, priority: get_default_priority(name: providerName));
435 logDebug(QStringLiteral(" %1: (class: %2) loaded as %3").arg(args: fileName, args: className, args: providerName));
436 }
437 }
438#endif
439}
440
441bool ProviderManager::add(Provider *p, int priority)
442{
443 QMutexLocker locker(&providerMutex);
444
445 const QString providerName = p->name();
446
447 if (haveAlready(name: providerName)) {
448 logDebug(QStringLiteral("Directly adding: %1: already loaded provider, skipping").arg(a: providerName));
449 return false;
450 }
451
452 const int ver = p->qcaVersion();
453 if (!validVersion(ver)) {
454 QString errstr = QString::asprintf(format: "plugin version 0x%06x is in the future", ver);
455 logDebug(QStringLiteral("Directly adding: %1: %2").arg(args: providerName, args&: errstr));
456 return false;
457 }
458
459 ProviderItem *i = ProviderItem::fromClass(p);
460 addItem(i, priority);
461 logDebug(QStringLiteral("Directly adding: %1: loaded").arg(a: providerName));
462 return true;
463}
464
465bool ProviderManager::unload(const QString &name)
466{
467 for (int n = 0; n < providerItemList.count(); ++n) {
468 ProviderItem *i = providerItemList[n];
469 if (i->p && i->p->name() == name) {
470 if (i->initted())
471 i->p->deinit();
472
473 delete i;
474 providerItemList.removeAt(i: n);
475 providerList.removeAt(i: n);
476
477 logDebug(QStringLiteral("Unloaded: %1").arg(a: name));
478 return true;
479 }
480 }
481
482 return false;
483}
484
485void ProviderManager::unloadAll()
486{
487 foreach (ProviderItem *i, providerItemList) {
488 if (i->initted())
489 i->p->deinit();
490 }
491
492 while (!providerItemList.isEmpty()) {
493 ProviderItem *i = providerItemList.first();
494 const QString name = i->p->name();
495 delete i;
496 providerItemList.removeFirst();
497 providerList.removeFirst();
498
499 logDebug(QStringLiteral("Unloaded: %1").arg(a: name));
500 }
501}
502
503void ProviderManager::setDefault(Provider *p)
504{
505 QMutexLocker locker(&providerMutex);
506
507 if (def)
508 delete def;
509 def = p;
510 if (def) {
511 def->init();
512 QVariantMap conf = getProviderConfig_internal(p: def);
513 if (!conf.isEmpty())
514 def->configChanged(config: conf);
515 }
516}
517
518Provider *ProviderManager::find(Provider *_p) const
519{
520 ProviderItem *i = nullptr;
521 Provider *p = nullptr;
522
523 providerMutex.lock();
524 if (_p == def) {
525 p = def;
526 } else {
527 for (int n = 0; n < providerItemList.count(); ++n) {
528 ProviderItem *pi = providerItemList[n];
529 if (pi->p && pi->p == _p) {
530 i = pi;
531 p = pi->p;
532 break;
533 }
534 }
535 }
536 providerMutex.unlock();
537
538 if (i)
539 i->ensureInit();
540 return p;
541}
542
543Provider *ProviderManager::find(const QString &name) const
544{
545 ProviderItem *i = nullptr;
546 Provider *p = nullptr;
547
548 providerMutex.lock();
549 if (def && name == def->name()) {
550 p = def;
551 } else {
552 for (int n = 0; n < providerItemList.count(); ++n) {
553 ProviderItem *pi = providerItemList[n];
554 if (pi->p && pi->p->name() == name) {
555 i = pi;
556 p = pi->p;
557 break;
558 }
559 }
560 }
561 providerMutex.unlock();
562
563 if (i)
564 i->ensureInit();
565 return p;
566}
567
568Provider *ProviderManager::findFor(const QString &name, const QString &type) const
569{
570 if (name.isEmpty()) {
571 providerMutex.lock();
572 const QList<ProviderItem *> list = providerItemList;
573 providerMutex.unlock();
574
575 // find the first one that can do it
576 for (int n = 0; n < list.count(); ++n) {
577 ProviderItem *pi = list[n];
578 pi->ensureInit();
579 if (pi->p && pi->p->features().contains(str: type))
580 return pi->p;
581 }
582
583 // try the default provider as a last resort
584 providerMutex.lock();
585 Provider *p = def;
586 providerMutex.unlock();
587 if (p && p->features().contains(str: type))
588 return p;
589
590 return nullptr;
591 } else {
592 Provider *p = find(name);
593 if (p && p->features().contains(str: type))
594 return p;
595 return nullptr;
596 }
597}
598
599void ProviderManager::changePriority(const QString &name, int priority)
600{
601 QMutexLocker locker(&providerMutex);
602
603 ProviderItem *i = nullptr;
604 int n = 0;
605 for (; n < providerItemList.count(); ++n) {
606 ProviderItem *pi = providerItemList[n];
607 if (pi->p && pi->p->name() == name) {
608 i = pi;
609 break;
610 }
611 }
612 if (!i)
613 return;
614
615 providerItemList.removeAt(i: n);
616 providerList.removeAt(i: n);
617
618 addItem(i, priority);
619}
620
621int ProviderManager::getPriority(const QString &name)
622{
623 QMutexLocker locker(&providerMutex);
624
625 ProviderItem *i = nullptr;
626 for (int n = 0; n < providerItemList.count(); ++n) {
627 ProviderItem *pi = providerItemList[n];
628 if (pi->p && pi->p->name() == name) {
629 i = pi;
630 break;
631 }
632 }
633 if (!i)
634 return -1;
635
636 return i->priority;
637}
638
639QStringList ProviderManager::allFeatures() const
640{
641 QStringList featureList;
642
643 providerMutex.lock();
644 Provider *p = def;
645 providerMutex.unlock();
646 if (p)
647 featureList = p->features();
648
649 providerMutex.lock();
650 const QList<ProviderItem *> list = providerItemList;
651 providerMutex.unlock();
652 for (int n = 0; n < list.count(); ++n) {
653 ProviderItem *i = list[n];
654 if (i->p)
655 mergeFeatures(a: &featureList, b: i->p->features());
656 }
657
658 return featureList;
659}
660
661ProviderList ProviderManager::providers() const
662{
663 QMutexLocker locker(&providerMutex);
664
665 return providerList;
666}
667
668QString ProviderManager::diagnosticText() const
669{
670 QMutexLocker locker(&logMutex);
671
672 return dtext;
673}
674
675void ProviderManager::appendDiagnosticText(const QString &str)
676{
677 QMutexLocker locker(&logMutex);
678
679 dtext += str;
680 dtext = truncate_log(in: dtext, size: 20000);
681}
682
683void ProviderManager::clearDiagnosticText()
684{
685 QMutexLocker locker(&logMutex);
686
687 dtext = QString();
688}
689
690void ProviderManager::addItem(ProviderItem *item, int priority)
691{
692 if (priority < 0) {
693 // for -1, make the priority the same as the last item
694 if (!providerItemList.isEmpty()) {
695 const ProviderItem *last = providerItemList.last();
696 item->priority = last->priority;
697 } else
698 item->priority = 0;
699
700 providerItemList.append(t: item);
701 providerList.append(t: item->p);
702 } else {
703 // place the item before any other items with same or greater priority
704 int n = 0;
705 for (; n < providerItemList.count(); ++n) {
706 const ProviderItem *i = providerItemList[n];
707 if (i->priority >= priority)
708 break;
709 }
710
711 item->priority = priority;
712 providerItemList.insert(i: n, t: item);
713 providerList.insert(i: n, t: item->p);
714 }
715}
716
717bool ProviderManager::haveAlready(const QString &name) const
718{
719 if (def && name == def->name())
720 return true;
721
722 for (int n = 0; n < providerItemList.count(); ++n) {
723 ProviderItem *pi = providerItemList[n];
724 if (pi->p && pi->p->name() == name)
725 return true;
726 }
727
728 return false;
729}
730
731void ProviderManager::mergeFeatures(QStringList *a, const QStringList &b)
732{
733 for (const QString &s : b) {
734 if (!a->contains(str: s))
735 a->append(t: s);
736 }
737}
738
739int ProviderManager::get_default_priority(const QString &name) const
740{
741 const QStringList list = plugin_priorities(defaultProvider: def);
742 foreach (const QString &s, list) {
743 // qca_default already sanity checks the strings
744 const int n = s.indexOf(c: QLatin1Char(':'));
745 const QString sname = s.mid(position: 0, n);
746#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
747 const int spriority = QStringView(s).mid(pos: n + 1).toInt();
748#else
749 const int spriority = s.midRef(n + 1).toInt();
750#endif
751 if (sname == name)
752 return spriority;
753 }
754 return -1;
755}
756
757}
758

source code of qca/src/qca_plugin.cpp