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 // check settings version
95 // so far, we only have 1
97 if (s.version() != 1) {
98 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
101 // Set up storage and authentication backends
102 registerStorageBackends();
103 registerAuthenticators();
105 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
106 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
109 QVariantMap db_connectionProperties;
111 QString auth_authenticator;
112 QVariantMap auth_properties;
114 bool writeError = false;
116 if (config_from_environment) {
117 db_backend = environment.value("DB_BACKEND");
118 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
123 QVariantMap dbsettings = cs.storageSettings().toMap();
124 db_backend = dbsettings.value("Backend").toString();
125 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
127 QVariantMap authSettings = cs.authSettings().toMap();
128 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
129 auth_properties = authSettings.value("AuthProperties").toMap();
131 writeError = !cs.isWritable();
135 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
137 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
140 catch (ExitException) {
145 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
147 if (Quassel::isOptionSet("select-backend")) {
148 success &= selectBackend(Quassel::optionValue("select-backend"));
150 if (Quassel::isOptionSet("select-authenticator")) {
151 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
153 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
157 if (config_from_environment) {
159 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
161 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
164 catch (ExitException e) {
165 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
169 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
173 if (_registeredStorageBackends.empty()) {
174 throw ExitException{EXIT_FAILURE,
175 tr("Could not initialize any storage backend! Exiting...\n"
176 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
177 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
182 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
185 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
189 if (Quassel::isOptionSet("add-user")) {
190 bool success = createUser();
191 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
194 if (Quassel::isOptionSet("change-userpass")) {
195 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
196 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
199 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
200 if (_strictIdentEnabled) {
204 if (Quassel::isOptionSet("oidentd")) {
205 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
209 if (Quassel::isOptionSet("ident-daemon")) {
210 _identServer = new IdentServer(this);
213 Quassel::registerReloadHandler([]() {
214 // Currently, only reloading SSL certificates and the sysident cache is supported
215 if (Core::instance()) {
216 Core::instance()->cacheSysIdent();
217 Core::instance()->reloadCerts();
223 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
224 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
227 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
228 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
230 if (!startListening()) {
231 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
234 if (_configured && !Quassel::isOptionSet("norestore")) {
235 Core::restoreState();
240 if (_pendingInternalConnection) {
241 connectInternalPeer(_pendingInternalConnection);
242 _pendingInternalConnection = {};
247 void Core::initAsync()
252 catch (ExitException e) {
253 emit exitRequested(e.exitCode, e.errorString);
258 void Core::shutdown()
260 quInfo() << "Core shutting down...";
264 for (auto &&client : _connectingClients) {
265 client->deleteLater();
267 _connectingClients.clear();
269 if (_sessions.isEmpty()) {
270 emit shutdownComplete();
274 for (auto &&session : _sessions) {
275 connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
281 void Core::onSessionShutdown(SessionThread *session)
283 _sessions.take(_sessions.key(session))->deleteLater();
284 if (_sessions.isEmpty()) {
285 quInfo() << "Core shutdown complete!";
286 emit shutdownComplete();
291 /*** Session Restore ***/
293 void Core::saveState()
296 QVariantList activeSessions;
297 for (auto &&user : instance()->_sessions.keys())
298 activeSessions << QVariant::fromValue<UserId>(user);
299 _storage->setCoreState(activeSessions);
304 void Core::restoreState()
307 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
310 if (_sessions.count()) {
311 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
316 /* We don't check, since we are at the first version since switching to Git
317 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
319 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
324 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
325 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
327 if (activeSessions.count() > 0) {
328 quInfo() << "Restoring previous core state...";
329 for(auto &&v : activeSessions) {
330 UserId user = v.value<UserId>();
331 sessionForUser(user, true);
339 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
341 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
345 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
348 return tr("Core is already configured! Not configuring again...");
350 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
351 return tr("Admin user or password not set.");
354 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
355 return tr("Could not setup storage!");
358 quInfo() << "Selected authenticator:" << authenticator;
359 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
361 return tr("Could not setup authenticator!");
364 catch (ExitException e) {
365 // Event loop is running, so trigger an exit rather than throwing an exception
366 QCoreApplication::exit(e.exitCode);
367 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
370 if (!saveBackendSettings(backend, setupData)) {
371 return tr("Could not save backend settings, probably a permission problem.");
373 saveAuthenticatorSettings(authenticator, authSetupData);
375 quInfo() << qPrintable(tr("Creating admin user..."));
376 _storage->addUser(adminUser, adminPassword);
378 startListening(); // TODO check when we need this
383 QString Core::setupCoreForInternalUsage()
385 Q_ASSERT(!_registeredStorageBackends.empty());
387 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
389 for (int i = 0; i < 10; i++) {
391 pass += qrand() % 10;
394 // mono client currently needs sqlite
395 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
399 /*** Storage Handling ***/
401 template<typename Storage>
402 void Core::registerStorageBackend()
404 auto backend = makeDeferredShared<Storage>(this);
405 if (backend->isAvailable())
406 _registeredStorageBackends.emplace_back(std::move(backend));
408 backend->deleteLater();
412 void Core::registerStorageBackends()
414 if (_registeredStorageBackends.empty()) {
415 registerStorageBackend<SqliteStorage>();
416 registerStorageBackend<PostgreSqlStorage>();
421 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
423 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
424 [backendId](const DeferredSharedPtr<Storage> &backend) {
425 return backend->displayName() == backendId;
427 return it != _registeredStorageBackends.end() ? *it : nullptr;
431 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
432 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
434 if (backend.isEmpty()) {
435 quWarning() << "No storage backend selected!";
439 auto storage = storageBackend(backend);
441 qCritical() << "Selected storage backend is not available:" << backend;
445 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
447 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
448 switch (storageState) {
449 case Storage::NeedsSetup:
451 return false; // trigger setup process
452 if (storage->setup(settings, environment, loadFromEnvironment))
453 return initStorage(backend, settings, environment, loadFromEnvironment, false);
456 case Storage::NotAvailable:
458 // If initialization wasn't successful, we quit to keep from coming up unconfigured
459 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
461 qCritical() << "Selected storage backend is not available:" << backend;
464 case Storage::IsReady:
465 // delete all other backends
466 _registeredStorageBackends.clear();
467 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
468 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
471 _storage = std::move(storage);
476 void Core::syncStorage()
483 /*** Storage Access ***/
484 bool Core::createNetwork(UserId user, NetworkInfo &info)
486 NetworkId networkId = instance()->_storage->createNetwork(user, info);
487 if (!networkId.isValid())
490 info.networkId = networkId;
495 /*** Authenticators ***/
497 // Authentication handling, now independent from storage.
498 template<typename Authenticator>
499 void Core::registerAuthenticator()
501 auto authenticator = makeDeferredShared<Authenticator>(this);
502 if (authenticator->isAvailable())
503 _registeredAuthenticators.emplace_back(std::move(authenticator));
505 authenticator->deleteLater();
509 void Core::registerAuthenticators()
511 if (_registeredAuthenticators.empty()) {
512 registerAuthenticator<SqlAuthenticator>();
514 registerAuthenticator<LdapAuthenticator>();
520 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
522 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
523 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
524 return authenticator->backendId() == backendId;
526 return it != _registeredAuthenticators.end() ? *it : nullptr;
530 // FIXME: Apparently, this is the legacy way of initting storage backends?
531 // If there's a not-legacy way, it should be used here
532 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
533 const QProcessEnvironment &environment, bool loadFromEnvironment,
536 if (backend.isEmpty()) {
537 quWarning() << "No authenticator selected!";
541 auto auth = authenticator(backend);
543 qCritical() << "Selected auth backend is not available:" << backend;
547 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
549 case Authenticator::NeedsSetup:
551 return false; // trigger setup process
552 if (auth->setup(settings, environment, loadFromEnvironment))
553 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
556 case Authenticator::NotAvailable:
558 // If initialization wasn't successful, we quit to keep from coming up unconfigured
559 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
561 qCritical() << "Selected auth backend is not available:" << backend;
564 case Authenticator::IsReady:
565 // delete all other backends
566 _registeredAuthenticators.clear();
569 _authenticator = std::move(auth);
574 /*** Network Management ***/
576 bool Core::sslSupported()
579 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
580 return sslServer && sslServer->isCertValid();
587 bool Core::reloadCerts()
590 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
591 bool retv4 = sslServerv4->reloadCerts();
593 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
594 bool retv6 = sslServerv6->reloadCerts();
596 return retv4 && retv6;
598 // SSL not supported, don't mark configuration reload as failed
604 void Core::cacheSysIdent()
606 if (isConfigured()) {
607 _authUserNames = _storage->getAllAuthUserNames();
612 QString Core::strictSysIdent(UserId user) const
614 if (_authUserNames.contains(user)) {
615 return _authUserNames[user];
618 // A new user got added since we last pulled our cache from the database.
619 // There's no way to avoid a database hit - we don't even know the authname!
620 instance()->cacheSysIdent();
622 if (_authUserNames.contains(user)) {
623 return _authUserNames[user];
626 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
627 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
628 return "unknown"; // Should we just terminate the program instead?
632 bool Core::startListening()
634 // in mono mode we only start a local port if a port is specified in the cli call
635 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
638 bool success = false;
639 uint port = Quassel::optionValue("port").toUInt();
641 const QString listen = Quassel::optionValue("listen");
642 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
643 if (listen_list.size() > 0) {
644 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
646 if (!addr.setAddress(listen_term)) {
647 qCritical() << qPrintable(
648 tr("Invalid listen address %1")
653 switch (addr.protocol()) {
654 case QAbstractSocket::IPv6Protocol:
655 if (_v6server.listen(addr, port)) {
656 quInfo() << qPrintable(
657 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
658 .arg(addr.toString())
659 .arg(_v6server.serverPort())
660 .arg(Quassel::buildInfo().protocolVersion)
665 quWarning() << qPrintable(
666 tr("Could not open IPv6 interface %1:%2: %3")
667 .arg(addr.toString())
669 .arg(_v6server.errorString()));
671 case QAbstractSocket::IPv4Protocol:
672 if (_server.listen(addr, port)) {
673 quInfo() << qPrintable(
674 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
675 .arg(addr.toString())
676 .arg(_server.serverPort())
677 .arg(Quassel::buildInfo().protocolVersion)
682 // if v6 succeeded on Any, the port will be already in use - don't display the error then
683 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
684 quWarning() << qPrintable(
685 tr("Could not open IPv4 interface %1:%2: %3")
686 .arg(addr.toString())
688 .arg(_server.errorString()));
692 qCritical() << qPrintable(
693 tr("Invalid listen address %1, unknown network protocol")
702 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
705 _identServer->startListening();
712 void Core::stopListening(const QString &reason)
715 _identServer->stopListening(reason);
718 bool wasListening = false;
719 if (_server.isListening()) {
723 if (_v6server.isListening()) {
728 if (reason.isEmpty())
729 quInfo() << "No longer listening for GUI clients.";
731 quInfo() << qPrintable(reason);
736 void Core::incomingConnection()
738 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
740 while (server->hasPendingConnections()) {
741 QTcpSocket *socket = server->nextPendingConnection();
743 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
744 _connectingClients.insert(handler);
746 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
747 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
748 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
750 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
753 stopListening(tr("Closing server for basic setup."));
759 // Potentially called during the initialization phase (before handing the connection off to the session)
760 void Core::clientDisconnected()
762 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
765 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
766 _connectingClients.remove(handler);
767 handler->deleteLater();
769 // make server listen again if still not configured
774 // TODO remove unneeded sessions - if necessary/possible...
775 // Suggestion: kill sessions if they are not connected to any network and client.
779 void Core::setupClientSession(RemotePeer *peer, UserId uid)
781 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
784 // From now on everything is handled by the client session
785 disconnect(handler, 0, this, 0);
786 _connectingClients.remove(handler);
787 handler->deleteLater();
789 // Find or create session for validated user
792 // as we are currently handling an event triggered by incoming data on this socket
793 // it is unsafe to directly move the socket to the client thread.
794 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
798 void Core::customEvent(QEvent *event)
800 if (event->type() == AddClientEventId) {
801 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
802 addClientHelper(addClientEvent->peer, addClientEvent->userId);
808 void Core::addClientHelper(RemotePeer *peer, UserId uid)
810 // Find or create session for validated user
811 SessionThread *session = sessionForUser(uid);
812 session->addClient(peer);
816 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
818 if (_initialized && peer) {
819 setupInternalClientSession(peer);
822 _pendingInternalConnection = peer;
827 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
831 auto errorString = setupCoreForInternalUsage();
832 if (!errorString.isEmpty()) {
833 emit exitRequested(EXIT_FAILURE, errorString);
840 uid = _storage->internalUser();
843 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
844 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
849 quWarning() << "Client peer went away, not starting a session";
853 InternalPeer *corePeer = new InternalPeer(this);
854 corePeer->setPeer(clientPeer);
855 clientPeer->setPeer(corePeer);
857 // Find or create session for validated user
858 SessionThread *sessionThread = sessionForUser(uid);
859 sessionThread->addClient(corePeer);
863 SessionThread *Core::sessionForUser(UserId uid, bool restore)
865 if (_sessions.contains(uid))
866 return _sessions[uid];
868 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
872 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
874 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
878 QVariantList Core::backendInfo()
880 instance()->registerStorageBackends();
882 QVariantList backendInfos;
883 for (auto &&backend : instance()->_registeredStorageBackends) {
885 v["BackendId"] = backend->backendId();
886 v["DisplayName"] = backend->displayName();
887 v["Description"] = backend->description();
888 v["SetupData"] = backend->setupData(); // ignored by legacy clients
890 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
891 const auto &setupData = backend->setupData();
892 QStringList setupKeys;
893 QVariantMap setupDefaults;
894 for (int i = 0; i + 2 < setupData.size(); i += 3) {
895 setupKeys << setupData[i].toString();
896 setupDefaults[setupData[i].toString()] = setupData[i + 2];
898 v["SetupKeys"] = setupKeys;
899 v["SetupDefaults"] = setupDefaults;
900 // TODO Protocol Break: Remove
901 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
909 QVariantList Core::authenticatorInfo()
911 instance()->registerAuthenticators();
913 QVariantList authInfos;
914 for(auto &&backend : instance()->_registeredAuthenticators) {
916 v["BackendId"] = backend->backendId();
917 v["DisplayName"] = backend->displayName();
918 v["Description"] = backend->description();
919 v["SetupData"] = backend->setupData();
925 // migration / backend selection
926 bool Core::selectBackend(const QString &backend)
928 // reregister all storage backends
929 registerStorageBackends();
930 auto storage = storageBackend(backend);
932 QStringList backends;
933 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
934 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
935 return backend->displayName();
937 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
938 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
942 QVariantMap settings = promptForSettings(storage.get());
944 Storage::State storageState = storage->init(settings);
945 switch (storageState) {
946 case Storage::IsReady:
947 if (!saveBackendSettings(backend, settings)) {
948 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
950 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
951 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
953 case Storage::NotAvailable:
954 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
956 case Storage::NeedsSetup:
957 if (!storage->setup(settings)) {
958 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
962 if (storage->init(settings) != Storage::IsReady) {
963 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
967 if (!saveBackendSettings(backend, settings)) {
968 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
970 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
974 // let's see if we have a current storage object we can migrate from
975 auto reader = getMigrationReader(_storage.get());
976 auto writer = getMigrationWriter(storage.get());
977 if (reader && writer) {
978 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
981 if (reader->migrateTo(writer.get())) {
982 qDebug() << "Migration finished!";
983 qDebug() << qPrintable(tr("Migration finished!"));
984 if (!saveBackendSettings(backend, settings)) {
985 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
990 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
994 // inform the user why we cannot merge
996 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
999 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
1002 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
1005 // so we were unable to merge, but let's create a user \o/
1006 _storage = std::move(storage);
1011 // TODO: I am not sure if this function is implemented correctly.
1012 // There is currently no concept of migraiton between auth backends.
1013 bool Core::selectAuthenticator(const QString &backend)
1015 // Register all authentication backends.
1016 registerAuthenticators();
1017 auto auther = authenticator(backend);
1019 QStringList authenticators;
1020 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1021 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1022 return authenticator->displayName();
1024 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1025 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1029 QVariantMap settings = promptForSettings(auther.get());
1031 Authenticator::State state = auther->init(settings);
1033 case Authenticator::IsReady:
1034 saveAuthenticatorSettings(backend, settings);
1035 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1037 case Authenticator::NotAvailable:
1038 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1040 case Authenticator::NeedsSetup:
1041 if (!auther->setup(settings)) {
1042 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1046 if (auther->init(settings) != Authenticator::IsReady) {
1047 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1051 saveAuthenticatorSettings(backend, settings);
1052 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1055 _authenticator = std::move(auther);
1060 bool Core::createUser()
1062 QTextStream out(stdout);
1063 QTextStream in(stdin);
1064 out << "Add a new user:" << endl;
1065 out << "Username: ";
1067 QString username = in.readLine().trimmed();
1070 out << "Password: ";
1072 QString password = in.readLine().trimmed();
1074 out << "Repeat Password: ";
1076 QString password2 = in.readLine().trimmed();
1080 if (password != password2) {
1081 quWarning() << "Passwords don't match!";
1084 if (password.isEmpty()) {
1085 quWarning() << "Password is empty!";
1089 if (_configured && _storage->addUser(username, password).isValid()) {
1090 out << "Added user " << username << " successfully!" << endl;
1094 quWarning() << "Unable to add user:" << qPrintable(username);
1100 bool Core::changeUserPass(const QString &username)
1102 QTextStream out(stdout);
1103 QTextStream in(stdin);
1104 UserId userId = _storage->getUserId(username);
1105 if (!userId.isValid()) {
1106 out << "User " << username << " does not exist." << endl;
1110 if (!canChangeUserPassword(userId)) {
1111 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1115 out << "Change password for user: " << username << endl;
1118 out << "New Password: ";
1120 QString password = in.readLine().trimmed();
1122 out << "Repeat Password: ";
1124 QString password2 = in.readLine().trimmed();
1128 if (password != password2) {
1129 quWarning() << "Passwords don't match!";
1132 if (password.isEmpty()) {
1133 quWarning() << "Password is empty!";
1137 if (_configured && _storage->updateUser(userId, password)) {
1138 out << "Password changed successfully!" << endl;
1142 quWarning() << "Failed to change password!";
1148 bool Core::changeUserPassword(UserId userId, const QString &password)
1150 if (!isConfigured() || !userId.isValid())
1153 if (!canChangeUserPassword(userId))
1156 return instance()->_storage->updateUser(userId, password);
1159 // TODO: this code isn't currently 100% optimal because the core
1160 // doesn't know it can have multiple auth providers configured (there aren't
1161 // multiple auth providers at the moment anyway) and we have hardcoded the
1162 // Database provider to be always allowed.
1163 bool Core::canChangeUserPassword(UserId userId)
1165 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1166 if (authProvider != "Database") {
1167 if (authProvider != instance()->_authenticator->backendId()) {
1170 else if (instance()->_authenticator->canChangePassword()) {
1178 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1183 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1185 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1189 return sqlStorage->createMigrationReader();
1193 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1198 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1200 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1204 return sqlStorage->createMigrationWriter();
1208 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1210 QVariantMap dbsettings;
1211 dbsettings["Backend"] = backend;
1212 dbsettings["ConnectionProperties"] = settings;
1213 CoreSettings s = CoreSettings();
1214 s.setStorageSettings(dbsettings);
1219 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1221 QVariantMap dbsettings;
1222 dbsettings["Authenticator"] = backend;
1223 dbsettings["AuthProperties"] = settings;
1224 CoreSettings().setAuthSettings(dbsettings);
1227 // Generic version of promptForSettings that doesn't care what *type* of
1228 // backend it runs over.
1229 template<typename Backend>
1230 QVariantMap Core::promptForSettings(const Backend *backend)
1232 QVariantMap settings;
1233 const QVariantList& setupData = backend->setupData();
1235 if (setupData.isEmpty())
1238 QTextStream out(stdout);
1239 QTextStream in(stdin);
1240 out << "Default values are in brackets" << endl;
1242 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1243 QString key = setupData[i].toString();
1244 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1246 bool noEcho = key.toLower().contains("password");
1250 QString input = in.readLine().trimmed();
1256 QVariant value{setupData[i+2]};
1257 if (!input.isEmpty()) {
1258 switch (value.type()) {
1260 value = input.toInt();
1266 settings[key] = value;
1273 void Core::stdInEcho(bool on)
1275 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1277 GetConsoleMode(hStdin, &mode);
1279 mode |= ENABLE_ECHO_INPUT;
1281 mode &= ~ENABLE_ECHO_INPUT;
1282 SetConsoleMode(hStdin, mode);
1286 void Core::stdInEcho(bool on)
1289 tcgetattr(STDIN_FILENO, &t);
1294 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1297 #endif /* Q_OS_WIN */