1 /***************************************************************************
2 * Copyright (C) 2005-2019 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
25 #include <QCoreApplication>
27 #include "coreauthhandler.h"
28 #include "coresession.h"
29 #include "coresettings.h"
30 #include "internalpeer.h"
32 #include "postgresqlstorage.h"
34 #include "sqlauthenticator.h"
35 #include "sqlitestorage.h"
40 # include "ldapauthenticator.h"
52 // ==============================
54 // ==============================
55 const int Core::AddClientEventId = QEvent::registerEventType();
57 class AddClientEvent : public QEvent
60 AddClientEvent(RemotePeer* p, UserId uid)
61 : QEvent(QEvent::Type(Core::AddClientEventId))
69 // ==============================
71 // ==============================
74 : Singleton<Core>{this}
78 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
79 // thread, they get moved with it
80 _server.setParent(this);
81 _v6server.setParent(this);
82 _storageSyncTimer.setParent(this);
87 qDeleteAll(_connectingClients);
88 qDeleteAll(_sessions);
94 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
96 // check settings version
97 // so far, we only have 1
99 if (s.version() != 1) {
100 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
103 // Set up storage and authentication backends
104 registerStorageBackends();
105 registerAuthenticators();
107 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
108 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
111 QVariantMap db_connectionProperties;
113 QString auth_authenticator;
114 QVariantMap auth_properties;
116 bool writeError = false;
118 if (config_from_environment) {
119 db_backend = environment.value("DB_BACKEND");
120 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
125 QVariantMap dbsettings = cs.storageSettings().toMap();
126 db_backend = dbsettings.value("Backend").toString();
127 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
129 QVariantMap authSettings = cs.authSettings().toMap();
130 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
131 auth_properties = authSettings.value("AuthProperties").toMap();
133 writeError = !cs.isWritable();
137 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
139 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
142 catch (ExitException) {
147 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
149 if (Quassel::isOptionSet("select-backend")) {
150 success &= selectBackend(Quassel::optionValue("select-backend"));
152 if (Quassel::isOptionSet("select-authenticator")) {
153 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
155 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
159 if (config_from_environment) {
161 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
163 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
166 catch (ExitException e) {
167 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
171 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
175 if (_registeredStorageBackends.empty()) {
176 throw ExitException{EXIT_FAILURE,
177 tr("Could not initialize any storage backend! Exiting...\n"
178 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
179 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
184 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
187 qInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
191 if (Quassel::isOptionSet("add-user")) {
192 bool success = createUser();
193 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
196 if (Quassel::isOptionSet("change-userpass")) {
197 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
198 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
201 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
202 if (_strictIdentEnabled) {
206 if (Quassel::isOptionSet("oidentd")) {
207 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
210 if (Quassel::isOptionSet("ident-daemon")) {
211 _identServer = new IdentServer(this);
214 Quassel::registerReloadHandler([]() {
215 // Currently, only reloading SSL certificates and the sysident cache is supported
216 if (Core::instance()) {
217 Core::instance()->cacheSysIdent();
218 Core::instance()->reloadCerts();
224 connect(&_storageSyncTimer, &QTimer::timeout, this, &Core::syncStorage);
225 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
228 connect(&_server, &QTcpServer::newConnection, this, &Core::incomingConnection);
229 connect(&_v6server, &QTcpServer::newConnection, this, &Core::incomingConnection);
231 if (!startListening()) {
232 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
235 if (_configured && !Quassel::isOptionSet("norestore")) {
236 Core::restoreState();
241 if (_pendingInternalConnection) {
242 connectInternalPeer(_pendingInternalConnection);
243 _pendingInternalConnection = {};
247 void Core::initAsync()
252 catch (ExitException e) {
253 emit exitRequested(e.exitCode, e.errorString);
257 void Core::shutdown()
259 qInfo() << "Core shutting down...";
263 for (auto&& client : _connectingClients) {
264 client->deleteLater();
266 _connectingClients.clear();
268 if (_sessions.isEmpty()) {
269 emit shutdownComplete();
273 for (auto&& session : _sessions) {
274 connect(session, &SessionThread::shutdownComplete, this, &Core::onSessionShutdown);
279 void Core::onSessionShutdown(SessionThread* session)
281 _sessions.take(_sessions.key(session))->deleteLater();
282 if (_sessions.isEmpty()) {
283 qInfo() << "Core shutdown complete!";
284 emit shutdownComplete();
288 /*** Session Restore ***/
290 void Core::saveState()
293 QVariantList activeSessions;
294 for (auto&& user : instance()->_sessions.keys())
295 activeSessions << QVariant::fromValue<UserId>(user);
296 _storage->setCoreState(activeSessions);
300 void Core::restoreState()
303 qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
306 if (_sessions.count()) {
307 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
312 /* We don't check, since we are at the first version since switching to Git
313 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
315 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
320 const QList<QVariant>& activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
321 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
323 if (activeSessions.count() > 0) {
324 qInfo() << "Restoring previous core state...";
325 for (auto&& v : activeSessions) {
326 UserId user = v.value<UserId>();
327 sessionForUser(user, true);
334 QString Core::setup(const QString& adminUser,
335 const QString& adminPassword,
336 const QString& backend,
337 const QVariantMap& setupData,
338 const QString& authenticator,
339 const QVariantMap& authSetupData)
341 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
344 QString Core::setupCore(const QString& adminUser,
345 const QString& adminPassword,
346 const QString& backend,
347 const QVariantMap& setupData,
348 const QString& authenticator,
349 const QVariantMap& authSetupData)
352 return tr("Core is already configured! Not configuring again...");
354 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
355 return tr("Admin user or password not set.");
358 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
359 return tr("Could not setup storage!");
362 qInfo() << "Selected authenticator:" << authenticator;
363 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) {
364 return tr("Could not setup authenticator!");
367 catch (ExitException e) {
368 // Event loop is running, so trigger an exit rather than throwing an exception
369 QCoreApplication::exit(e.exitCode);
370 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
373 if (!saveBackendSettings(backend, setupData)) {
374 return tr("Could not save backend settings, probably a permission problem.");
376 saveAuthenticatorSettings(authenticator, authSetupData);
378 qInfo() << qPrintable(tr("Creating admin user..."));
379 _storage->addUser(adminUser, adminPassword);
381 startListening(); // TODO check when we need this
385 QString Core::setupCoreForInternalUsage()
387 Q_ASSERT(!_registeredStorageBackends.empty());
389 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
391 for (int i = 0; i < 10; i++) {
393 pass += qrand() % 10;
396 // mono client currently needs sqlite
397 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
400 /*** Storage Handling ***/
402 template<typename Storage>
403 void Core::registerStorageBackend()
405 auto backend = makeDeferredShared<Storage>(this);
406 if (backend->isAvailable())
407 _registeredStorageBackends.emplace_back(std::move(backend));
409 backend->deleteLater();
412 void Core::registerStorageBackends()
414 if (_registeredStorageBackends.empty()) {
415 registerStorageBackend<SqliteStorage>();
416 registerStorageBackend<PostgreSqlStorage>();
420 DeferredSharedPtr<Storage> Core::storageBackend(const QString& backendId) const
422 auto it = std::find_if(_registeredStorageBackends.begin(),
423 _registeredStorageBackends.end(),
424 [backendId](const DeferredSharedPtr<Storage>& backend) { return backend->displayName() == backendId; });
425 return it != _registeredStorageBackends.end() ? *it : nullptr;
428 bool Core::initStorage(
429 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
431 if (backend.isEmpty()) {
432 qWarning() << "No storage backend selected!";
436 auto storage = storageBackend(backend);
438 qCritical() << "Selected storage backend is not available:" << backend;
442 connect(storage.get(), &Storage::dbUpgradeInProgress, this, &Core::dbUpgradeInProgress);
444 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
445 switch (storageState) {
446 case Storage::NeedsSetup:
448 return false; // trigger setup process
449 if (storage->setup(settings, environment, loadFromEnvironment))
450 return initStorage(backend, settings, environment, loadFromEnvironment, false);
453 case Storage::NotAvailable:
455 // If initialization wasn't successful, we quit to keep from coming up unconfigured
456 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
458 qCritical() << "Selected storage backend is not available:" << backend;
461 case Storage::IsReady:
462 // delete all other backends
463 _registeredStorageBackends.clear();
464 connect(storage.get(), &Storage::bufferInfoUpdated, this, &Core::bufferInfoUpdated);
467 _storage = std::move(storage);
471 void Core::syncStorage()
477 /*** Storage Access ***/
478 bool Core::createNetwork(UserId user, NetworkInfo& info)
480 NetworkId networkId = instance()->_storage->createNetwork(user, info);
481 if (!networkId.isValid())
484 info.networkId = networkId;
488 /*** Authenticators ***/
490 // Authentication handling, now independent from storage.
491 template<typename Authenticator>
492 void Core::registerAuthenticator()
494 auto authenticator = makeDeferredShared<Authenticator>(this);
495 if (authenticator->isAvailable())
496 _registeredAuthenticators.emplace_back(std::move(authenticator));
498 authenticator->deleteLater();
501 void Core::registerAuthenticators()
503 if (_registeredAuthenticators.empty()) {
504 registerAuthenticator<SqlAuthenticator>();
506 registerAuthenticator<LdapAuthenticator>();
511 DeferredSharedPtr<Authenticator> Core::authenticator(const QString& backendId) const
513 auto it = std::find_if(_registeredAuthenticators.begin(),
514 _registeredAuthenticators.end(),
515 [backendId](const DeferredSharedPtr<Authenticator>& authenticator) {
516 return authenticator->backendId() == backendId;
518 return it != _registeredAuthenticators.end() ? *it : nullptr;
521 // FIXME: Apparently, this is the legacy way of initting storage backends?
522 // If there's a not-legacy way, it should be used here
523 bool Core::initAuthenticator(
524 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
526 if (backend.isEmpty()) {
527 qWarning() << "No authenticator selected!";
531 auto auth = authenticator(backend);
533 qCritical() << "Selected auth backend is not available:" << backend;
537 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
539 case Authenticator::NeedsSetup:
541 return false; // trigger setup process
542 if (auth->setup(settings, environment, loadFromEnvironment))
543 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
546 case Authenticator::NotAvailable:
548 // If initialization wasn't successful, we quit to keep from coming up unconfigured
549 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
551 qCritical() << "Selected auth backend is not available:" << backend;
554 case Authenticator::IsReady:
555 // delete all other backends
556 _registeredAuthenticators.clear();
559 _authenticator = std::move(auth);
563 /*** Network Management ***/
565 bool Core::sslSupported()
568 auto* sslServer = qobject_cast<SslServer*>(&instance()->_server);
569 return sslServer && sslServer->isCertValid();
575 bool Core::reloadCerts()
578 auto* sslServerv4 = qobject_cast<SslServer*>(&_server);
579 bool retv4 = sslServerv4->reloadCerts();
581 auto* sslServerv6 = qobject_cast<SslServer*>(&_v6server);
582 bool retv6 = sslServerv6->reloadCerts();
584 return retv4 && retv6;
586 // SSL not supported, don't mark configuration reload as failed
591 void Core::cacheSysIdent()
593 if (isConfigured()) {
594 _authUserNames = _storage->getAllAuthUserNames();
598 QString Core::strictSysIdent(UserId user) const
600 if (_authUserNames.contains(user)) {
601 return _authUserNames[user];
604 // A new user got added since we last pulled our cache from the database.
605 // There's no way to avoid a database hit - we don't even know the authname!
606 instance()->cacheSysIdent();
608 if (_authUserNames.contains(user)) {
609 return _authUserNames[user];
612 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
613 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
614 return "unknown"; // Should we just terminate the program instead?
617 bool Core::startListening()
619 // in mono mode we only start a local port if a port is specified in the cli call
620 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
623 bool success = false;
624 uint port = Quassel::optionValue("port").toUInt();
626 const QString listen = Quassel::optionValue("listen");
627 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
628 if (listen_list.size() > 0) {
629 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
631 if (!addr.setAddress(listen_term)) {
632 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
635 switch (addr.protocol()) {
636 case QAbstractSocket::IPv6Protocol:
637 if (_v6server.listen(addr, port)) {
638 qInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
639 .arg(addr.toString())
640 .arg(_v6server.serverPort())
641 .arg(Quassel::buildInfo().protocolVersion));
645 qWarning() << qPrintable(tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
647 case QAbstractSocket::IPv4Protocol:
648 if (_server.listen(addr, port)) {
649 qInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
650 .arg(addr.toString())
651 .arg(_server.serverPort())
652 .arg(Quassel::buildInfo().protocolVersion));
656 // if v6 succeeded on Any, the port will be already in use - don't display the error then
657 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
658 qWarning() << qPrintable(tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
662 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
669 qCritical() << qPrintable(tr("Could not open any network interfaces to listen on!"));
672 _identServer->startListening();
678 void Core::stopListening(const QString& reason)
681 _identServer->stopListening(reason);
684 bool wasListening = false;
685 if (_server.isListening()) {
689 if (_v6server.isListening()) {
694 if (reason.isEmpty())
695 qInfo() << "No longer listening for GUI clients.";
697 qInfo() << qPrintable(reason);
701 void Core::incomingConnection()
703 auto* server = qobject_cast<QTcpServer*>(sender());
705 while (server->hasPendingConnections()) {
706 QTcpSocket* socket = server->nextPendingConnection();
708 auto* handler = new CoreAuthHandler(socket, this);
709 _connectingClients.insert(handler);
711 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
712 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
713 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
715 qInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
718 stopListening(tr("Closing server for basic setup."));
723 // Potentially called during the initialization phase (before handing the connection off to the session)
724 void Core::clientDisconnected()
726 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
729 qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
730 _connectingClients.remove(handler);
731 handler->deleteLater();
733 // make server listen again if still not configured
738 // TODO remove unneeded sessions - if necessary/possible...
739 // Suggestion: kill sessions if they are not connected to any network and client.
742 void Core::setupClientSession(RemotePeer* peer, UserId uid)
744 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
747 // From now on everything is handled by the client session
748 disconnect(handler, nullptr, this, nullptr);
749 _connectingClients.remove(handler);
750 handler->deleteLater();
752 // Find or create session for validated user
755 // as we are currently handling an event triggered by incoming data on this socket
756 // it is unsafe to directly move the socket to the client thread.
757 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
760 void Core::customEvent(QEvent* event)
762 if (event->type() == AddClientEventId) {
763 auto* addClientEvent = static_cast<AddClientEvent*>(event);
764 addClientHelper(addClientEvent->peer, addClientEvent->userId);
769 void Core::addClientHelper(RemotePeer* peer, UserId uid)
771 // Find or create session for validated user
772 SessionThread* session = sessionForUser(uid);
773 session->addClient(peer);
776 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
778 if (_initialized && peer) {
779 setupInternalClientSession(peer);
782 _pendingInternalConnection = peer;
786 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
790 auto errorString = setupCoreForInternalUsage();
791 if (!errorString.isEmpty()) {
792 emit exitRequested(EXIT_FAILURE, errorString);
799 uid = _storage->internalUser();
802 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
803 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
808 qWarning() << "Client peer went away, not starting a session";
812 auto* corePeer = new InternalPeer(this);
813 corePeer->setPeer(clientPeer);
814 clientPeer->setPeer(corePeer);
816 // Find or create session for validated user
817 SessionThread* sessionThread = sessionForUser(uid);
818 sessionThread->addClient(corePeer);
821 SessionThread* Core::sessionForUser(UserId uid, bool restore)
823 if (_sessions.contains(uid))
824 return _sessions[uid];
826 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
829 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
831 qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
834 QVariantList Core::backendInfo()
836 instance()->registerStorageBackends();
838 QVariantList backendInfos;
839 for (auto&& backend : instance()->_registeredStorageBackends) {
841 v["BackendId"] = backend->backendId();
842 v["DisplayName"] = backend->displayName();
843 v["Description"] = backend->description();
844 v["SetupData"] = backend->setupData(); // ignored by legacy clients
846 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
847 const auto& setupData = backend->setupData();
848 QStringList setupKeys;
849 QVariantMap setupDefaults;
850 for (int i = 0; i + 2 < setupData.size(); i += 3) {
851 setupKeys << setupData[i].toString();
852 setupDefaults[setupData[i].toString()] = setupData[i + 2];
854 v["SetupKeys"] = setupKeys;
855 v["SetupDefaults"] = setupDefaults;
856 // TODO Protocol Break: Remove
857 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
864 QVariantList Core::authenticatorInfo()
866 instance()->registerAuthenticators();
868 QVariantList authInfos;
869 for (auto&& backend : instance()->_registeredAuthenticators) {
871 v["BackendId"] = backend->backendId();
872 v["DisplayName"] = backend->displayName();
873 v["Description"] = backend->description();
874 v["SetupData"] = backend->setupData();
880 // migration / backend selection
881 bool Core::selectBackend(const QString& backend)
883 // reregister all storage backends
884 registerStorageBackends();
885 auto storage = storageBackend(backend);
887 QStringList backends;
888 std::transform(_registeredStorageBackends.begin(),
889 _registeredStorageBackends.end(),
890 std::back_inserter(backends),
891 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
892 qWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
893 qWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
897 QVariantMap settings = promptForSettings(storage.get());
899 Storage::State storageState = storage->init(settings);
900 switch (storageState) {
901 case Storage::IsReady:
902 if (!saveBackendSettings(backend, settings)) {
903 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
905 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
906 qWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
908 case Storage::NotAvailable:
909 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
911 case Storage::NeedsSetup:
912 if (!storage->setup(settings)) {
913 qWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
917 if (storage->init(settings) != Storage::IsReady) {
918 qWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
922 if (!saveBackendSettings(backend, settings)) {
923 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
925 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
929 // let's see if we have a current storage object we can migrate from
930 auto reader = getMigrationReader(_storage.get());
931 auto writer = getMigrationWriter(storage.get());
932 if (reader && writer) {
933 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
936 if (reader->migrateTo(writer.get())) {
937 qDebug() << "Migration finished!";
938 qDebug() << qPrintable(tr("Migration finished!"));
939 if (!saveBackendSettings(backend, settings)) {
940 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
945 qWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
949 // inform the user why we cannot merge
951 qWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
954 qWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
957 qWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
960 // so we were unable to merge, but let's create a user \o/
961 _storage = std::move(storage);
966 // TODO: I am not sure if this function is implemented correctly.
967 // There is currently no concept of migraiton between auth backends.
968 bool Core::selectAuthenticator(const QString& backend)
970 // Register all authentication backends.
971 registerAuthenticators();
972 auto auther = authenticator(backend);
974 QStringList authenticators;
975 std::transform(_registeredAuthenticators.begin(),
976 _registeredAuthenticators.end(),
977 std::back_inserter(authenticators),
978 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
979 qWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
980 qWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
984 QVariantMap settings = promptForSettings(auther.get());
986 Authenticator::State state = auther->init(settings);
988 case Authenticator::IsReady:
989 saveAuthenticatorSettings(backend, settings);
990 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
992 case Authenticator::NotAvailable:
993 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
995 case Authenticator::NeedsSetup:
996 if (!auther->setup(settings)) {
997 qWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1001 if (auther->init(settings) != Authenticator::IsReady) {
1002 qWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1006 saveAuthenticatorSettings(backend, settings);
1007 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1010 _authenticator = std::move(auther);
1014 bool Core::createUser()
1016 QTextStream out(stdout);
1017 QTextStream in(stdin);
1018 out << "Add a new user:" << endl;
1019 out << "Username: ";
1021 QString username = in.readLine().trimmed();
1024 out << "Password: ";
1026 QString password = in.readLine().trimmed();
1028 out << "Repeat Password: ";
1030 QString password2 = in.readLine().trimmed();
1034 if (password != password2) {
1035 qWarning() << "Passwords don't match!";
1038 if (password.isEmpty()) {
1039 qWarning() << "Password is empty!";
1043 if (_configured && _storage->addUser(username, password).isValid()) {
1044 out << "Added user " << username << " successfully!" << endl;
1048 qWarning() << "Unable to add user:" << qPrintable(username);
1053 bool Core::changeUserPass(const QString& username)
1055 QTextStream out(stdout);
1056 QTextStream in(stdin);
1057 UserId userId = _storage->getUserId(username);
1058 if (!userId.isValid()) {
1059 out << "User " << username << " does not exist." << endl;
1063 if (!canChangeUserPassword(userId)) {
1064 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1068 out << "Change password for user: " << username << endl;
1071 out << "New Password: ";
1073 QString password = in.readLine().trimmed();
1075 out << "Repeat Password: ";
1077 QString password2 = in.readLine().trimmed();
1081 if (password != password2) {
1082 qWarning() << "Passwords don't match!";
1085 if (password.isEmpty()) {
1086 qWarning() << "Password is empty!";
1090 if (_configured && _storage->updateUser(userId, password)) {
1091 out << "Password changed successfully!" << endl;
1095 qWarning() << "Failed to change password!";
1100 bool Core::changeUserPassword(UserId userId, const QString& password)
1102 if (!isConfigured() || !userId.isValid())
1105 if (!canChangeUserPassword(userId))
1108 return instance()->_storage->updateUser(userId, password);
1111 // TODO: this code isn't currently 100% optimal because the core
1112 // doesn't know it can have multiple auth providers configured (there aren't
1113 // multiple auth providers at the moment anyway) and we have hardcoded the
1114 // Database provider to be always allowed.
1115 bool Core::canChangeUserPassword(UserId userId)
1117 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1118 if (authProvider != "Database") {
1119 if (authProvider != instance()->_authenticator->backendId()) {
1122 else if (instance()->_authenticator->canChangePassword()) {
1129 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1134 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1136 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1140 return sqlStorage->createMigrationReader();
1143 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1148 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1150 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1154 return sqlStorage->createMigrationWriter();
1157 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1159 QVariantMap dbsettings;
1160 dbsettings["Backend"] = backend;
1161 dbsettings["ConnectionProperties"] = settings;
1162 CoreSettings s = CoreSettings();
1163 s.setStorageSettings(dbsettings);
1167 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1169 QVariantMap dbsettings;
1170 dbsettings["Authenticator"] = backend;
1171 dbsettings["AuthProperties"] = settings;
1172 CoreSettings().setAuthSettings(dbsettings);
1175 // Generic version of promptForSettings that doesn't care what *type* of
1176 // backend it runs over.
1177 template<typename Backend>
1178 QVariantMap Core::promptForSettings(const Backend* backend)
1180 QVariantMap settings;
1181 const QVariantList& setupData = backend->setupData();
1183 if (setupData.isEmpty())
1186 QTextStream out(stdout);
1187 QTextStream in(stdin);
1188 out << "Default values are in brackets" << endl;
1190 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1191 QString key = setupData[i].toString();
1192 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1194 bool noEcho = key.toLower().contains("password");
1198 QString input = in.readLine().trimmed();
1204 QVariant value{setupData[i + 2]};
1205 if (!input.isEmpty()) {
1206 switch (value.type()) {
1208 value = input.toInt();
1214 settings[key] = value;
1220 void Core::stdInEcho(bool on)
1222 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1224 GetConsoleMode(hStdin, &mode);
1226 mode |= ENABLE_ECHO_INPUT;
1228 mode &= ~ENABLE_ECHO_INPUT;
1229 SetConsoleMode(hStdin, mode);
1233 void Core::stdInEcho(bool on)
1236 tcgetattr(STDIN_FILENO, &t);
1241 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1244 #endif /* Q_OS_WIN */