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"
40 # include "ldapauthenticator.h"
52 // ==============================
54 // ==============================
55 const int Core::AddClientEventId = QEvent::registerEventType();
57 class AddClientEvent : public QEvent
60 AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
66 // ==============================
68 // ==============================
71 : Singleton<Core>{this}
73 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
74 // thread, they get moved with it
75 _server.setParent(this);
76 _v6server.setParent(this);
77 _storageSyncTimer.setParent(this);
83 qDeleteAll(_connectingClients);
84 qDeleteAll(_sessions);
91 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
93 // check settings version
94 // so far, we only have 1
96 if (s.version() != 1) {
97 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
100 // Set up storage and authentication backends
101 registerStorageBackends();
102 registerAuthenticators();
104 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
105 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
108 QVariantMap db_connectionProperties;
110 QString auth_authenticator;
111 QVariantMap auth_properties;
113 bool writeError = false;
115 if (config_from_environment) {
116 db_backend = environment.value("DB_BACKEND");
117 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
122 QVariantMap dbsettings = cs.storageSettings().toMap();
123 db_backend = dbsettings.value("Backend").toString();
124 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
126 QVariantMap authSettings = cs.authSettings().toMap();
127 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
128 auth_properties = authSettings.value("AuthProperties").toMap();
130 writeError = !cs.isWritable();
134 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
136 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
139 catch (ExitException) {
144 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
146 if (Quassel::isOptionSet("select-backend")) {
147 success &= selectBackend(Quassel::optionValue("select-backend"));
149 if (Quassel::isOptionSet("select-authenticator")) {
150 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
152 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
156 if (config_from_environment) {
158 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
160 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
163 catch (ExitException e) {
164 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
168 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
172 if (_registeredStorageBackends.empty()) {
173 throw ExitException{EXIT_FAILURE,
174 tr("Could not initialize any storage backend! Exiting...\n"
175 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
176 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
181 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
184 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
188 if (Quassel::isOptionSet("add-user")) {
189 bool success = createUser();
190 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
193 if (Quassel::isOptionSet("change-userpass")) {
194 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
195 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
198 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
199 if (_strictIdentEnabled) {
203 if (Quassel::isOptionSet("oidentd")) {
204 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
208 if (Quassel::isOptionSet("ident-daemon")) {
209 _identServer = new IdentServer(this);
212 Quassel::registerReloadHandler([]() {
213 // Currently, only reloading SSL certificates and the sysident cache is supported
214 if (Core::instance()) {
215 Core::instance()->cacheSysIdent();
216 Core::instance()->reloadCerts();
222 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
223 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
226 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
227 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
229 if (!startListening()) {
230 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
233 if (_configured && !Quassel::isOptionSet("norestore")) {
234 Core::restoreState();
239 if (_pendingInternalConnection) {
240 connectInternalPeer(_pendingInternalConnection);
241 _pendingInternalConnection = {};
246 void Core::initAsync()
251 catch (ExitException e) {
252 emit exitRequested(e.exitCode, e.errorString);
257 void Core::shutdown()
259 quInfo() << "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, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
280 void Core::onSessionShutdown(SessionThread *session)
282 _sessions.take(_sessions.key(session))->deleteLater();
283 if (_sessions.isEmpty()) {
284 quInfo() << "Core shutdown complete!";
285 emit shutdownComplete();
290 /*** Session Restore ***/
292 void Core::saveState()
295 QVariantList activeSessions;
296 for (auto &&user : instance()->_sessions.keys())
297 activeSessions << QVariant::fromValue<UserId>(user);
298 _storage->setCoreState(activeSessions);
303 void Core::restoreState()
306 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
309 if (_sessions.count()) {
310 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
315 /* We don't check, since we are at the first version since switching to Git
316 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
318 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
323 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
324 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
326 if (activeSessions.count() > 0) {
327 quInfo() << "Restoring previous core state...";
328 for(auto &&v : activeSessions) {
329 UserId user = v.value<UserId>();
330 sessionForUser(user, true);
338 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
340 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
344 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
347 return tr("Core is already configured! Not configuring again...");
349 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
350 return tr("Admin user or password not set.");
353 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
354 return tr("Could not setup storage!");
357 quInfo() << "Selected authenticator:" << authenticator;
358 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
360 return tr("Could not setup authenticator!");
363 catch (ExitException e) {
364 // Event loop is running, so trigger an exit rather than throwing an exception
365 QCoreApplication::exit(e.exitCode);
366 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
369 if (!saveBackendSettings(backend, setupData)) {
370 return tr("Could not save backend settings, probably a permission problem.");
372 saveAuthenticatorSettings(authenticator, authSetupData);
374 quInfo() << qPrintable(tr("Creating admin user..."));
375 _storage->addUser(adminUser, adminPassword);
377 startListening(); // TODO check when we need this
382 QString Core::setupCoreForInternalUsage()
384 Q_ASSERT(!_registeredStorageBackends.empty());
386 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
388 for (int i = 0; i < 10; i++) {
390 pass += qrand() % 10;
393 // mono client currently needs sqlite
394 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
398 /*** Storage Handling ***/
400 template<typename Storage>
401 void Core::registerStorageBackend()
403 auto backend = makeDeferredShared<Storage>(this);
404 if (backend->isAvailable())
405 _registeredStorageBackends.emplace_back(std::move(backend));
407 backend->deleteLater();
411 void Core::registerStorageBackends()
413 if (_registeredStorageBackends.empty()) {
414 registerStorageBackend<SqliteStorage>();
415 registerStorageBackend<PostgreSqlStorage>();
420 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
422 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
423 [backendId](const DeferredSharedPtr<Storage> &backend) {
424 return backend->displayName() == backendId;
426 return it != _registeredStorageBackends.end() ? *it : nullptr;
430 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
431 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
433 if (backend.isEmpty()) {
434 quWarning() << "No storage backend selected!";
438 auto storage = storageBackend(backend);
440 qCritical() << "Selected storage backend is not available:" << backend;
444 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
446 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
447 switch (storageState) {
448 case Storage::NeedsSetup:
450 return false; // trigger setup process
451 if (storage->setup(settings, environment, loadFromEnvironment))
452 return initStorage(backend, settings, environment, loadFromEnvironment, false);
455 case Storage::NotAvailable:
457 // If initialization wasn't successful, we quit to keep from coming up unconfigured
458 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
460 qCritical() << "Selected storage backend is not available:" << backend;
463 case Storage::IsReady:
464 // delete all other backends
465 _registeredStorageBackends.clear();
466 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
467 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
470 _storage = std::move(storage);
475 void Core::syncStorage()
482 /*** Storage Access ***/
483 bool Core::createNetwork(UserId user, NetworkInfo &info)
485 NetworkId networkId = instance()->_storage->createNetwork(user, info);
486 if (!networkId.isValid())
489 info.networkId = networkId;
494 /*** Authenticators ***/
496 // Authentication handling, now independent from storage.
497 template<typename Authenticator>
498 void Core::registerAuthenticator()
500 auto authenticator = makeDeferredShared<Authenticator>(this);
501 if (authenticator->isAvailable())
502 _registeredAuthenticators.emplace_back(std::move(authenticator));
504 authenticator->deleteLater();
508 void Core::registerAuthenticators()
510 if (_registeredAuthenticators.empty()) {
511 registerAuthenticator<SqlAuthenticator>();
513 registerAuthenticator<LdapAuthenticator>();
519 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
521 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
522 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
523 return authenticator->backendId() == backendId;
525 return it != _registeredAuthenticators.end() ? *it : nullptr;
529 // FIXME: Apparently, this is the legacy way of initting storage backends?
530 // If there's a not-legacy way, it should be used here
531 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
532 const QProcessEnvironment &environment, bool loadFromEnvironment,
535 if (backend.isEmpty()) {
536 quWarning() << "No authenticator selected!";
540 auto auth = authenticator(backend);
542 qCritical() << "Selected auth backend is not available:" << backend;
546 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
548 case Authenticator::NeedsSetup:
550 return false; // trigger setup process
551 if (auth->setup(settings, environment, loadFromEnvironment))
552 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
555 case Authenticator::NotAvailable:
557 // If initialization wasn't successful, we quit to keep from coming up unconfigured
558 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
560 qCritical() << "Selected auth backend is not available:" << backend;
563 case Authenticator::IsReady:
564 // delete all other backends
565 _registeredAuthenticators.clear();
568 _authenticator = std::move(auth);
573 /*** Network Management ***/
575 bool Core::sslSupported()
578 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
579 return sslServer && sslServer->isCertValid();
586 bool Core::reloadCerts()
589 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
590 bool retv4 = sslServerv4->reloadCerts();
592 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
593 bool retv6 = sslServerv6->reloadCerts();
595 return retv4 && retv6;
597 // SSL not supported, don't mark configuration reload as failed
603 void Core::cacheSysIdent()
605 if (isConfigured()) {
606 _authUserNames = _storage->getAllAuthUserNames();
611 QString Core::strictSysIdent(UserId user) const
613 if (_authUserNames.contains(user)) {
614 return _authUserNames[user];
617 // A new user got added since we last pulled our cache from the database.
618 // There's no way to avoid a database hit - we don't even know the authname!
619 instance()->cacheSysIdent();
621 if (_authUserNames.contains(user)) {
622 return _authUserNames[user];
625 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
626 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
627 return "unknown"; // Should we just terminate the program instead?
631 bool Core::startListening()
633 // in mono mode we only start a local port if a port is specified in the cli call
634 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
637 bool success = false;
638 uint port = Quassel::optionValue("port").toUInt();
640 const QString listen = Quassel::optionValue("listen");
641 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
642 if (listen_list.size() > 0) {
643 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
645 if (!addr.setAddress(listen_term)) {
646 qCritical() << qPrintable(
647 tr("Invalid listen address %1")
652 switch (addr.protocol()) {
653 case QAbstractSocket::IPv6Protocol:
654 if (_v6server.listen(addr, port)) {
655 quInfo() << qPrintable(
656 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
657 .arg(addr.toString())
658 .arg(_v6server.serverPort())
659 .arg(Quassel::buildInfo().protocolVersion)
664 quWarning() << qPrintable(
665 tr("Could not open IPv6 interface %1:%2: %3")
666 .arg(addr.toString())
668 .arg(_v6server.errorString()));
670 case QAbstractSocket::IPv4Protocol:
671 if (_server.listen(addr, port)) {
672 quInfo() << qPrintable(
673 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
674 .arg(addr.toString())
675 .arg(_server.serverPort())
676 .arg(Quassel::buildInfo().protocolVersion)
681 // if v6 succeeded on Any, the port will be already in use - don't display the error then
682 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
683 quWarning() << qPrintable(
684 tr("Could not open IPv4 interface %1:%2: %3")
685 .arg(addr.toString())
687 .arg(_server.errorString()));
691 qCritical() << qPrintable(
692 tr("Invalid listen address %1, unknown network protocol")
701 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
704 _identServer->startListening();
711 void Core::stopListening(const QString &reason)
714 _identServer->stopListening(reason);
717 bool wasListening = false;
718 if (_server.isListening()) {
722 if (_v6server.isListening()) {
727 if (reason.isEmpty())
728 quInfo() << "No longer listening for GUI clients.";
730 quInfo() << qPrintable(reason);
735 void Core::incomingConnection()
737 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
739 while (server->hasPendingConnections()) {
740 QTcpSocket *socket = server->nextPendingConnection();
742 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
743 _connectingClients.insert(handler);
745 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
746 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
747 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
749 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
752 stopListening(tr("Closing server for basic setup."));
758 // Potentially called during the initialization phase (before handing the connection off to the session)
759 void Core::clientDisconnected()
761 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
764 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
765 _connectingClients.remove(handler);
766 handler->deleteLater();
768 // make server listen again if still not configured
773 // TODO remove unneeded sessions - if necessary/possible...
774 // Suggestion: kill sessions if they are not connected to any network and client.
778 void Core::setupClientSession(RemotePeer *peer, UserId uid)
780 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
783 // From now on everything is handled by the client session
784 disconnect(handler, 0, this, 0);
785 _connectingClients.remove(handler);
786 handler->deleteLater();
788 // Find or create session for validated user
791 // as we are currently handling an event triggered by incoming data on this socket
792 // it is unsafe to directly move the socket to the client thread.
793 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
797 void Core::customEvent(QEvent *event)
799 if (event->type() == AddClientEventId) {
800 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
801 addClientHelper(addClientEvent->peer, addClientEvent->userId);
807 void Core::addClientHelper(RemotePeer *peer, UserId uid)
809 // Find or create session for validated user
810 SessionThread *session = sessionForUser(uid);
811 session->addClient(peer);
815 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
817 if (_initialized && peer) {
818 setupInternalClientSession(peer);
821 _pendingInternalConnection = peer;
826 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
830 auto errorString = setupCoreForInternalUsage();
831 if (!errorString.isEmpty()) {
832 emit exitRequested(EXIT_FAILURE, errorString);
839 uid = _storage->internalUser();
842 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
843 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
848 quWarning() << "Client peer went away, not starting a session";
852 InternalPeer *corePeer = new InternalPeer(this);
853 corePeer->setPeer(clientPeer);
854 clientPeer->setPeer(corePeer);
856 // Find or create session for validated user
857 SessionThread *sessionThread = sessionForUser(uid);
858 sessionThread->addClient(corePeer);
862 SessionThread *Core::sessionForUser(UserId uid, bool restore)
864 if (_sessions.contains(uid))
865 return _sessions[uid];
867 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
871 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
873 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
877 QVariantList Core::backendInfo()
879 instance()->registerStorageBackends();
881 QVariantList backendInfos;
882 for (auto &&backend : instance()->_registeredStorageBackends) {
884 v["BackendId"] = backend->backendId();
885 v["DisplayName"] = backend->displayName();
886 v["Description"] = backend->description();
887 v["SetupData"] = backend->setupData(); // ignored by legacy clients
889 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
890 const auto &setupData = backend->setupData();
891 QStringList setupKeys;
892 QVariantMap setupDefaults;
893 for (int i = 0; i + 2 < setupData.size(); i += 3) {
894 setupKeys << setupData[i].toString();
895 setupDefaults[setupData[i].toString()] = setupData[i + 2];
897 v["SetupKeys"] = setupKeys;
898 v["SetupDefaults"] = setupDefaults;
899 // TODO Protocol Break: Remove
900 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
908 QVariantList Core::authenticatorInfo()
910 instance()->registerAuthenticators();
912 QVariantList authInfos;
913 for(auto &&backend : instance()->_registeredAuthenticators) {
915 v["BackendId"] = backend->backendId();
916 v["DisplayName"] = backend->displayName();
917 v["Description"] = backend->description();
918 v["SetupData"] = backend->setupData();
924 // migration / backend selection
925 bool Core::selectBackend(const QString &backend)
927 // reregister all storage backends
928 registerStorageBackends();
929 auto storage = storageBackend(backend);
931 QStringList backends;
932 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
933 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
934 return backend->displayName();
936 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
937 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
941 QVariantMap settings = promptForSettings(storage.get());
943 Storage::State storageState = storage->init(settings);
944 switch (storageState) {
945 case Storage::IsReady:
946 if (!saveBackendSettings(backend, settings)) {
947 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
949 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
950 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
952 case Storage::NotAvailable:
953 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
955 case Storage::NeedsSetup:
956 if (!storage->setup(settings)) {
957 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
961 if (storage->init(settings) != Storage::IsReady) {
962 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
966 if (!saveBackendSettings(backend, settings)) {
967 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
969 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
973 // let's see if we have a current storage object we can migrate from
974 auto reader = getMigrationReader(_storage.get());
975 auto writer = getMigrationWriter(storage.get());
976 if (reader && writer) {
977 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
980 if (reader->migrateTo(writer.get())) {
981 qDebug() << "Migration finished!";
982 qDebug() << qPrintable(tr("Migration finished!"));
983 if (!saveBackendSettings(backend, settings)) {
984 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
989 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
993 // inform the user why we cannot merge
995 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
998 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
1001 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
1004 // so we were unable to merge, but let's create a user \o/
1005 _storage = std::move(storage);
1010 // TODO: I am not sure if this function is implemented correctly.
1011 // There is currently no concept of migraiton between auth backends.
1012 bool Core::selectAuthenticator(const QString &backend)
1014 // Register all authentication backends.
1015 registerAuthenticators();
1016 auto auther = authenticator(backend);
1018 QStringList authenticators;
1019 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1020 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1021 return authenticator->displayName();
1023 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1024 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1028 QVariantMap settings = promptForSettings(auther.get());
1030 Authenticator::State state = auther->init(settings);
1032 case Authenticator::IsReady:
1033 saveAuthenticatorSettings(backend, settings);
1034 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1036 case Authenticator::NotAvailable:
1037 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1039 case Authenticator::NeedsSetup:
1040 if (!auther->setup(settings)) {
1041 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1045 if (auther->init(settings) != Authenticator::IsReady) {
1046 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1050 saveAuthenticatorSettings(backend, settings);
1051 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1054 _authenticator = std::move(auther);
1059 bool Core::createUser()
1061 QTextStream out(stdout);
1062 QTextStream in(stdin);
1063 out << "Add a new user:" << endl;
1064 out << "Username: ";
1066 QString username = in.readLine().trimmed();
1069 out << "Password: ";
1071 QString password = in.readLine().trimmed();
1073 out << "Repeat Password: ";
1075 QString password2 = in.readLine().trimmed();
1079 if (password != password2) {
1080 quWarning() << "Passwords don't match!";
1083 if (password.isEmpty()) {
1084 quWarning() << "Password is empty!";
1088 if (_configured && _storage->addUser(username, password).isValid()) {
1089 out << "Added user " << username << " successfully!" << endl;
1093 quWarning() << "Unable to add user:" << qPrintable(username);
1099 bool Core::changeUserPass(const QString &username)
1101 QTextStream out(stdout);
1102 QTextStream in(stdin);
1103 UserId userId = _storage->getUserId(username);
1104 if (!userId.isValid()) {
1105 out << "User " << username << " does not exist." << endl;
1109 if (!canChangeUserPassword(userId)) {
1110 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1114 out << "Change password for user: " << username << endl;
1117 out << "New Password: ";
1119 QString password = in.readLine().trimmed();
1121 out << "Repeat Password: ";
1123 QString password2 = in.readLine().trimmed();
1127 if (password != password2) {
1128 quWarning() << "Passwords don't match!";
1131 if (password.isEmpty()) {
1132 quWarning() << "Password is empty!";
1136 if (_configured && _storage->updateUser(userId, password)) {
1137 out << "Password changed successfully!" << endl;
1141 quWarning() << "Failed to change password!";
1147 bool Core::changeUserPassword(UserId userId, const QString &password)
1149 if (!isConfigured() || !userId.isValid())
1152 if (!canChangeUserPassword(userId))
1155 return instance()->_storage->updateUser(userId, password);
1158 // TODO: this code isn't currently 100% optimal because the core
1159 // doesn't know it can have multiple auth providers configured (there aren't
1160 // multiple auth providers at the moment anyway) and we have hardcoded the
1161 // Database provider to be always allowed.
1162 bool Core::canChangeUserPassword(UserId userId)
1164 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1165 if (authProvider != "Database") {
1166 if (authProvider != instance()->_authenticator->backendId()) {
1169 else if (instance()->_authenticator->canChangePassword()) {
1177 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1182 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1184 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1188 return sqlStorage->createMigrationReader();
1192 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1197 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1199 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1203 return sqlStorage->createMigrationWriter();
1207 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1209 QVariantMap dbsettings;
1210 dbsettings["Backend"] = backend;
1211 dbsettings["ConnectionProperties"] = settings;
1212 CoreSettings s = CoreSettings();
1213 s.setStorageSettings(dbsettings);
1218 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1220 QVariantMap dbsettings;
1221 dbsettings["Authenticator"] = backend;
1222 dbsettings["AuthProperties"] = settings;
1223 CoreSettings().setAuthSettings(dbsettings);
1226 // Generic version of promptForSettings that doesn't care what *type* of
1227 // backend it runs over.
1228 template<typename Backend>
1229 QVariantMap Core::promptForSettings(const Backend *backend)
1231 QVariantMap settings;
1232 const QVariantList& setupData = backend->setupData();
1234 if (setupData.isEmpty())
1237 QTextStream out(stdout);
1238 QTextStream in(stdin);
1239 out << "Default values are in brackets" << endl;
1241 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1242 QString key = setupData[i].toString();
1243 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1245 bool noEcho = key.toLower().contains("password");
1249 QString input = in.readLine().trimmed();
1255 QVariant value{setupData[i+2]};
1256 if (!input.isEmpty()) {
1257 switch (value.type()) {
1259 value = input.toInt();
1265 settings[key] = value;
1272 void Core::stdInEcho(bool on)
1274 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1276 GetConsoleMode(hStdin, &mode);
1278 mode |= ENABLE_ECHO_INPUT;
1280 mode &= ~ENABLE_ECHO_INPUT;
1281 SetConsoleMode(hStdin, mode);
1285 void Core::stdInEcho(bool on)
1288 tcgetattr(STDIN_FILENO, &t);
1293 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1296 #endif /* Q_OS_WIN */