1 /***************************************************************************
2 * Copyright (C) 2005-2018 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 ***************************************************************************/
23 #include <QCoreApplication>
26 #include "coreauthhandler.h"
27 #include "coresession.h"
28 #include "coresettings.h"
29 #include "internalpeer.h"
30 #include "logmessage.h"
32 #include "postgresqlstorage.h"
34 #include "sqlauthenticator.h"
35 #include "sqlitestorage.h"
39 // Currently building with LDAP bindings is optional.
41 #include "ldapauthenticator.h"
53 // ==============================
55 // ==============================
56 const int Core::AddClientEventId = QEvent::registerEventType();
58 class AddClientEvent : public QEvent
61 AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
67 // ==============================
69 // ==============================
72 : Singleton<Core>{this}
74 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
75 // thread, they get moved with it
76 _server.setParent(this);
77 _v6server.setParent(this);
78 _storageSyncTimer.setParent(this);
84 qDeleteAll(_connectingClients);
85 qDeleteAll(_sessions);
92 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
94 if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
95 Quassel::loadTranslation(QLocale::system());
98 // check settings version
99 // so far, we only have 1
101 if (s.version() != 1) {
102 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
105 // Set up storage and authentication backends
106 registerStorageBackends();
107 registerAuthenticators();
109 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
110 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
113 QVariantMap db_connectionProperties;
115 QString auth_authenticator;
116 QVariantMap auth_properties;
118 bool writeError = false;
120 if (config_from_environment) {
121 db_backend = environment.value("DB_BACKEND");
122 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
127 QVariantMap dbsettings = cs.storageSettings().toMap();
128 db_backend = dbsettings.value("Backend").toString();
129 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
131 QVariantMap authSettings = cs.authSettings().toMap();
132 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
133 auth_properties = authSettings.value("AuthProperties").toMap();
135 writeError = !cs.isWritable();
139 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
141 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
144 catch (ExitException) {
149 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
151 if (Quassel::isOptionSet("select-backend")) {
152 success &= selectBackend(Quassel::optionValue("select-backend"));
154 if (Quassel::isOptionSet("select-authenticator")) {
155 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
157 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
161 if (config_from_environment) {
163 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
165 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
168 catch (ExitException e) {
169 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
173 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
177 if (_registeredStorageBackends.empty()) {
178 throw ExitException{EXIT_FAILURE,
179 tr("Could not initialize any storage backend! Exiting...\n"
180 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
181 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
186 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
189 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
193 if (Quassel::isOptionSet("add-user")) {
194 bool success = createUser();
195 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
198 if (Quassel::isOptionSet("change-userpass")) {
199 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
200 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
203 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
204 if (_strictIdentEnabled) {
208 if (Quassel::isOptionSet("oidentd")) {
209 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
213 if (Quassel::isOptionSet("ident-daemon")) {
214 _identServer = new IdentServer(this);
217 Quassel::registerReloadHandler([]() {
218 // Currently, only reloading SSL certificates and the sysident cache is supported
219 if (Core::instance()) {
220 Core::instance()->cacheSysIdent();
221 Core::instance()->reloadCerts();
227 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
228 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
231 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
232 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
234 if (!startListening()) {
235 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
238 if (_configured && !Quassel::isOptionSet("norestore")) {
239 Core::restoreState();
244 if (_pendingInternalConnection) {
245 connectInternalPeer(_pendingInternalConnection);
246 _pendingInternalConnection = {};
251 void Core::initAsync()
256 catch (ExitException e) {
257 emit exitRequested(e.exitCode, e.errorString);
262 void Core::shutdown()
264 quInfo() << "Core shutting down...";
268 for (auto &&client : _connectingClients) {
269 client->deleteLater();
271 _connectingClients.clear();
273 if (_sessions.isEmpty()) {
274 emit shutdownComplete();
278 for (auto &&session : _sessions) {
279 connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
285 void Core::onSessionShutdown(SessionThread *session)
287 _sessions.take(_sessions.key(session))->deleteLater();
288 if (_sessions.isEmpty()) {
289 quInfo() << "Core shutdown complete!";
290 emit shutdownComplete();
295 /*** Session Restore ***/
297 void Core::saveState()
300 QVariantList activeSessions;
301 for (auto &&user : instance()->_sessions.keys())
302 activeSessions << QVariant::fromValue<UserId>(user);
303 _storage->setCoreState(activeSessions);
308 void Core::restoreState()
311 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
314 if (_sessions.count()) {
315 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
320 /* We don't check, since we are at the first version since switching to Git
321 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
323 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
328 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
329 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
331 if (activeSessions.count() > 0) {
332 quInfo() << "Restoring previous core state...";
333 for(auto &&v : activeSessions) {
334 UserId user = v.value<UserId>();
335 sessionForUser(user, true);
343 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
345 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
349 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, 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 quInfo() << "Selected authenticator:" << authenticator;
363 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
365 return tr("Could not setup authenticator!");
368 catch (ExitException e) {
369 // Event loop is running, so trigger an exit rather than throwing an exception
370 QCoreApplication::exit(e.exitCode);
371 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
374 if (!saveBackendSettings(backend, setupData)) {
375 return tr("Could not save backend settings, probably a permission problem.");
377 saveAuthenticatorSettings(authenticator, authSetupData);
379 quInfo() << qPrintable(tr("Creating admin user..."));
380 _storage->addUser(adminUser, adminPassword);
382 startListening(); // TODO check when we need this
387 QString Core::setupCoreForInternalUsage()
389 Q_ASSERT(!_registeredStorageBackends.empty());
391 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
393 for (int i = 0; i < 10; i++) {
395 pass += qrand() % 10;
398 // mono client currently needs sqlite
399 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
403 /*** Storage Handling ***/
405 template<typename Storage>
406 void Core::registerStorageBackend()
408 auto backend = makeDeferredShared<Storage>(this);
409 if (backend->isAvailable())
410 _registeredStorageBackends.emplace_back(std::move(backend));
412 backend->deleteLater();
416 void Core::registerStorageBackends()
418 if (_registeredStorageBackends.empty()) {
419 registerStorageBackend<SqliteStorage>();
420 registerStorageBackend<PostgreSqlStorage>();
425 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
427 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
428 [backendId](const DeferredSharedPtr<Storage> &backend) {
429 return backend->displayName() == backendId;
431 return it != _registeredStorageBackends.end() ? *it : nullptr;
435 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
436 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
438 if (backend.isEmpty()) {
439 quWarning() << "No storage backend selected!";
443 auto storage = storageBackend(backend);
445 qCritical() << "Selected storage backend is not available:" << backend;
449 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
451 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
452 switch (storageState) {
453 case Storage::NeedsSetup:
455 return false; // trigger setup process
456 if (storage->setup(settings, environment, loadFromEnvironment))
457 return initStorage(backend, settings, environment, loadFromEnvironment, false);
460 case Storage::NotAvailable:
462 // If initialization wasn't successful, we quit to keep from coming up unconfigured
463 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
465 qCritical() << "Selected storage backend is not available:" << backend;
468 case Storage::IsReady:
469 // delete all other backends
470 _registeredStorageBackends.clear();
471 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
472 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
475 _storage = std::move(storage);
480 void Core::syncStorage()
487 /*** Storage Access ***/
488 bool Core::createNetwork(UserId user, NetworkInfo &info)
490 NetworkId networkId = instance()->_storage->createNetwork(user, info);
491 if (!networkId.isValid())
494 info.networkId = networkId;
499 /*** Authenticators ***/
501 // Authentication handling, now independent from storage.
502 template<typename Authenticator>
503 void Core::registerAuthenticator()
505 auto authenticator = makeDeferredShared<Authenticator>(this);
506 if (authenticator->isAvailable())
507 _registeredAuthenticators.emplace_back(std::move(authenticator));
509 authenticator->deleteLater();
513 void Core::registerAuthenticators()
515 if (_registeredAuthenticators.empty()) {
516 registerAuthenticator<SqlAuthenticator>();
518 registerAuthenticator<LdapAuthenticator>();
524 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
526 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
527 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
528 return authenticator->backendId() == backendId;
530 return it != _registeredAuthenticators.end() ? *it : nullptr;
534 // FIXME: Apparently, this is the legacy way of initting storage backends?
535 // If there's a not-legacy way, it should be used here
536 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
537 const QProcessEnvironment &environment, bool loadFromEnvironment,
540 if (backend.isEmpty()) {
541 quWarning() << "No authenticator selected!";
545 auto auth = authenticator(backend);
547 qCritical() << "Selected auth backend is not available:" << backend;
551 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
553 case Authenticator::NeedsSetup:
555 return false; // trigger setup process
556 if (auth->setup(settings, environment, loadFromEnvironment))
557 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
560 case Authenticator::NotAvailable:
562 // If initialization wasn't successful, we quit to keep from coming up unconfigured
563 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
565 qCritical() << "Selected auth backend is not available:" << backend;
568 case Authenticator::IsReady:
569 // delete all other backends
570 _registeredAuthenticators.clear();
573 _authenticator = std::move(auth);
578 /*** Network Management ***/
580 bool Core::sslSupported()
583 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
584 return sslServer && sslServer->isCertValid();
591 bool Core::reloadCerts()
594 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
595 bool retv4 = sslServerv4->reloadCerts();
597 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
598 bool retv6 = sslServerv6->reloadCerts();
600 return retv4 && retv6;
602 // SSL not supported, don't mark configuration reload as failed
608 void Core::cacheSysIdent()
610 if (isConfigured()) {
611 _authUserNames = _storage->getAllAuthUserNames();
616 QString Core::strictSysIdent(UserId user) const
618 if (_authUserNames.contains(user)) {
619 return _authUserNames[user];
622 // A new user got added since we last pulled our cache from the database.
623 // There's no way to avoid a database hit - we don't even know the authname!
624 instance()->cacheSysIdent();
626 if (_authUserNames.contains(user)) {
627 return _authUserNames[user];
630 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
631 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
632 return "unknown"; // Should we just terminate the program instead?
636 bool Core::startListening()
638 // in mono mode we only start a local port if a port is specified in the cli call
639 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
642 bool success = false;
643 uint port = Quassel::optionValue("port").toUInt();
645 const QString listen = Quassel::optionValue("listen");
646 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
647 if (listen_list.size() > 0) {
648 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
650 if (!addr.setAddress(listen_term)) {
651 qCritical() << qPrintable(
652 tr("Invalid listen address %1")
657 switch (addr.protocol()) {
658 case QAbstractSocket::IPv6Protocol:
659 if (_v6server.listen(addr, port)) {
660 quInfo() << qPrintable(
661 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
662 .arg(addr.toString())
663 .arg(_v6server.serverPort())
664 .arg(Quassel::buildInfo().protocolVersion)
669 quWarning() << qPrintable(
670 tr("Could not open IPv6 interface %1:%2: %3")
671 .arg(addr.toString())
673 .arg(_v6server.errorString()));
675 case QAbstractSocket::IPv4Protocol:
676 if (_server.listen(addr, port)) {
677 quInfo() << qPrintable(
678 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
679 .arg(addr.toString())
680 .arg(_server.serverPort())
681 .arg(Quassel::buildInfo().protocolVersion)
686 // if v6 succeeded on Any, the port will be already in use - don't display the error then
687 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
688 quWarning() << qPrintable(
689 tr("Could not open IPv4 interface %1:%2: %3")
690 .arg(addr.toString())
692 .arg(_server.errorString()));
696 qCritical() << qPrintable(
697 tr("Invalid listen address %1, unknown network protocol")
706 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
709 _identServer->startListening();
716 void Core::stopListening(const QString &reason)
719 _identServer->stopListening(reason);
722 bool wasListening = false;
723 if (_server.isListening()) {
727 if (_v6server.isListening()) {
732 if (reason.isEmpty())
733 quInfo() << "No longer listening for GUI clients.";
735 quInfo() << qPrintable(reason);
740 void Core::incomingConnection()
742 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
744 while (server->hasPendingConnections()) {
745 QTcpSocket *socket = server->nextPendingConnection();
747 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
748 _connectingClients.insert(handler);
750 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
751 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
752 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
754 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
757 stopListening(tr("Closing server for basic setup."));
763 // Potentially called during the initialization phase (before handing the connection off to the session)
764 void Core::clientDisconnected()
766 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
769 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
770 _connectingClients.remove(handler);
771 handler->deleteLater();
773 // make server listen again if still not configured
778 // TODO remove unneeded sessions - if necessary/possible...
779 // Suggestion: kill sessions if they are not connected to any network and client.
783 void Core::setupClientSession(RemotePeer *peer, UserId uid)
785 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
788 // From now on everything is handled by the client session
789 disconnect(handler, 0, this, 0);
790 _connectingClients.remove(handler);
791 handler->deleteLater();
793 // Find or create session for validated user
796 // as we are currently handling an event triggered by incoming data on this socket
797 // it is unsafe to directly move the socket to the client thread.
798 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
802 void Core::customEvent(QEvent *event)
804 if (event->type() == AddClientEventId) {
805 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
806 addClientHelper(addClientEvent->peer, addClientEvent->userId);
812 void Core::addClientHelper(RemotePeer *peer, UserId uid)
814 // Find or create session for validated user
815 SessionThread *session = sessionForUser(uid);
816 session->addClient(peer);
820 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
822 if (_initialized && peer) {
823 setupInternalClientSession(peer);
826 _pendingInternalConnection = peer;
831 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
835 auto errorString = setupCoreForInternalUsage();
836 if (!errorString.isEmpty()) {
837 emit exitRequested(EXIT_FAILURE, errorString);
844 uid = _storage->internalUser();
847 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
848 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
853 quWarning() << "Client peer went away, not starting a session";
857 InternalPeer *corePeer = new InternalPeer(this);
858 corePeer->setPeer(clientPeer);
859 clientPeer->setPeer(corePeer);
861 // Find or create session for validated user
862 SessionThread *sessionThread = sessionForUser(uid);
863 sessionThread->addClient(corePeer);
867 SessionThread *Core::sessionForUser(UserId uid, bool restore)
869 if (_sessions.contains(uid))
870 return _sessions[uid];
872 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
876 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
878 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
882 QVariantList Core::backendInfo()
884 instance()->registerStorageBackends();
886 QVariantList backendInfos;
887 for (auto &&backend : instance()->_registeredStorageBackends) {
889 v["BackendId"] = backend->backendId();
890 v["DisplayName"] = backend->displayName();
891 v["Description"] = backend->description();
892 v["SetupData"] = backend->setupData(); // ignored by legacy clients
894 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
895 const auto &setupData = backend->setupData();
896 QStringList setupKeys;
897 QVariantMap setupDefaults;
898 for (int i = 0; i + 2 < setupData.size(); i += 3) {
899 setupKeys << setupData[i].toString();
900 setupDefaults[setupData[i].toString()] = setupData[i + 2];
902 v["SetupKeys"] = setupKeys;
903 v["SetupDefaults"] = setupDefaults;
904 // TODO Protocol Break: Remove
905 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
913 QVariantList Core::authenticatorInfo()
915 instance()->registerAuthenticators();
917 QVariantList authInfos;
918 for(auto &&backend : instance()->_registeredAuthenticators) {
920 v["BackendId"] = backend->backendId();
921 v["DisplayName"] = backend->displayName();
922 v["Description"] = backend->description();
923 v["SetupData"] = backend->setupData();
929 // migration / backend selection
930 bool Core::selectBackend(const QString &backend)
932 // reregister all storage backends
933 registerStorageBackends();
934 auto storage = storageBackend(backend);
936 QStringList backends;
937 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
938 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
939 return backend->displayName();
941 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
942 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
946 QVariantMap settings = promptForSettings(storage.get());
948 Storage::State storageState = storage->init(settings);
949 switch (storageState) {
950 case Storage::IsReady:
951 if (!saveBackendSettings(backend, settings)) {
952 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
954 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
955 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
957 case Storage::NotAvailable:
958 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
960 case Storage::NeedsSetup:
961 if (!storage->setup(settings)) {
962 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
966 if (storage->init(settings) != Storage::IsReady) {
967 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
971 if (!saveBackendSettings(backend, settings)) {
972 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
974 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
978 // let's see if we have a current storage object we can migrate from
979 auto reader = getMigrationReader(_storage.get());
980 auto writer = getMigrationWriter(storage.get());
981 if (reader && writer) {
982 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
985 if (reader->migrateTo(writer.get())) {
986 qDebug() << "Migration finished!";
987 qDebug() << qPrintable(tr("Migration finished!"));
988 if (!saveBackendSettings(backend, settings)) {
989 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
994 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
998 // inform the user why we cannot merge
1000 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
1003 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
1006 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
1009 // so we were unable to merge, but let's create a user \o/
1010 _storage = std::move(storage);
1015 // TODO: I am not sure if this function is implemented correctly.
1016 // There is currently no concept of migraiton between auth backends.
1017 bool Core::selectAuthenticator(const QString &backend)
1019 // Register all authentication backends.
1020 registerAuthenticators();
1021 auto auther = authenticator(backend);
1023 QStringList authenticators;
1024 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1025 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1026 return authenticator->displayName();
1028 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1029 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1033 QVariantMap settings = promptForSettings(auther.get());
1035 Authenticator::State state = auther->init(settings);
1037 case Authenticator::IsReady:
1038 saveAuthenticatorSettings(backend, settings);
1039 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1041 case Authenticator::NotAvailable:
1042 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1044 case Authenticator::NeedsSetup:
1045 if (!auther->setup(settings)) {
1046 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1050 if (auther->init(settings) != Authenticator::IsReady) {
1051 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1055 saveAuthenticatorSettings(backend, settings);
1056 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1059 _authenticator = std::move(auther);
1064 bool Core::createUser()
1066 QTextStream out(stdout);
1067 QTextStream in(stdin);
1068 out << "Add a new user:" << endl;
1069 out << "Username: ";
1071 QString username = in.readLine().trimmed();
1074 out << "Password: ";
1076 QString password = in.readLine().trimmed();
1078 out << "Repeat Password: ";
1080 QString password2 = in.readLine().trimmed();
1084 if (password != password2) {
1085 quWarning() << "Passwords don't match!";
1088 if (password.isEmpty()) {
1089 quWarning() << "Password is empty!";
1093 if (_configured && _storage->addUser(username, password).isValid()) {
1094 out << "Added user " << username << " successfully!" << endl;
1098 quWarning() << "Unable to add user:" << qPrintable(username);
1104 bool Core::changeUserPass(const QString &username)
1106 QTextStream out(stdout);
1107 QTextStream in(stdin);
1108 UserId userId = _storage->getUserId(username);
1109 if (!userId.isValid()) {
1110 out << "User " << username << " does not exist." << endl;
1114 if (!canChangeUserPassword(userId)) {
1115 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1119 out << "Change password for user: " << username << endl;
1122 out << "New Password: ";
1124 QString password = in.readLine().trimmed();
1126 out << "Repeat Password: ";
1128 QString password2 = in.readLine().trimmed();
1132 if (password != password2) {
1133 quWarning() << "Passwords don't match!";
1136 if (password.isEmpty()) {
1137 quWarning() << "Password is empty!";
1141 if (_configured && _storage->updateUser(userId, password)) {
1142 out << "Password changed successfully!" << endl;
1146 quWarning() << "Failed to change password!";
1152 bool Core::changeUserPassword(UserId userId, const QString &password)
1154 if (!isConfigured() || !userId.isValid())
1157 if (!canChangeUserPassword(userId))
1160 return instance()->_storage->updateUser(userId, password);
1163 // TODO: this code isn't currently 100% optimal because the core
1164 // doesn't know it can have multiple auth providers configured (there aren't
1165 // multiple auth providers at the moment anyway) and we have hardcoded the
1166 // Database provider to be always allowed.
1167 bool Core::canChangeUserPassword(UserId userId)
1169 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1170 if (authProvider != "Database") {
1171 if (authProvider != instance()->_authenticator->backendId()) {
1174 else if (instance()->_authenticator->canChangePassword()) {
1182 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1187 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1189 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1193 return sqlStorage->createMigrationReader();
1197 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1202 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1204 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1208 return sqlStorage->createMigrationWriter();
1212 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1214 QVariantMap dbsettings;
1215 dbsettings["Backend"] = backend;
1216 dbsettings["ConnectionProperties"] = settings;
1217 CoreSettings s = CoreSettings();
1218 s.setStorageSettings(dbsettings);
1223 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1225 QVariantMap dbsettings;
1226 dbsettings["Authenticator"] = backend;
1227 dbsettings["AuthProperties"] = settings;
1228 CoreSettings().setAuthSettings(dbsettings);
1231 // Generic version of promptForSettings that doesn't care what *type* of
1232 // backend it runs over.
1233 template<typename Backend>
1234 QVariantMap Core::promptForSettings(const Backend *backend)
1236 QVariantMap settings;
1237 const QVariantList& setupData = backend->setupData();
1239 if (setupData.isEmpty())
1242 QTextStream out(stdout);
1243 QTextStream in(stdin);
1244 out << "Default values are in brackets" << endl;
1246 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1247 QString key = setupData[i].toString();
1248 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1250 bool noEcho = key.toLower().contains("password");
1254 QString input = in.readLine().trimmed();
1260 QVariant value{setupData[i+2]};
1261 if (!input.isEmpty()) {
1262 switch (value.type()) {
1264 value = input.toInt();
1270 settings[key] = value;
1277 void Core::stdInEcho(bool on)
1279 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1281 GetConsoleMode(hStdin, &mode);
1283 mode |= ENABLE_ECHO_INPUT;
1285 mode &= ~ENABLE_ECHO_INPUT;
1286 SetConsoleMode(hStdin, mode);
1290 void Core::stdInEcho(bool on)
1293 tcgetattr(STDIN_FILENO, &t);
1298 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1301 #endif /* Q_OS_WIN */