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}
75 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
76 // thread, they get moved with it
77 _server.setParent(this);
78 _v6server.setParent(this);
79 _storageSyncTimer.setParent(this);
85 qDeleteAll(_connectingClients);
86 qDeleteAll(_sessions);
93 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
95 // check settings version
96 // so far, we only have 1
98 if (s.version() != 1) {
99 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
102 // Set up storage and authentication backends
103 registerStorageBackends();
104 registerAuthenticators();
106 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
107 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
110 QVariantMap db_connectionProperties;
112 QString auth_authenticator;
113 QVariantMap auth_properties;
115 bool writeError = false;
117 if (config_from_environment) {
118 db_backend = environment.value("DB_BACKEND");
119 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
124 QVariantMap dbsettings = cs.storageSettings().toMap();
125 db_backend = dbsettings.value("Backend").toString();
126 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
128 QVariantMap authSettings = cs.authSettings().toMap();
129 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
130 auth_properties = authSettings.value("AuthProperties").toMap();
132 writeError = !cs.isWritable();
136 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
138 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
141 catch (ExitException) {
146 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
148 if (Quassel::isOptionSet("select-backend")) {
149 success &= selectBackend(Quassel::optionValue("select-backend"));
151 if (Quassel::isOptionSet("select-authenticator")) {
152 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
154 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
158 if (config_from_environment) {
160 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
162 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
165 catch (ExitException e) {
166 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
170 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
174 if (_registeredStorageBackends.empty()) {
175 throw ExitException{EXIT_FAILURE,
176 tr("Could not initialize any storage backend! Exiting...\n"
177 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
178 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
183 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
186 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
190 if (Quassel::isOptionSet("add-user")) {
191 bool success = createUser();
192 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
195 if (Quassel::isOptionSet("change-userpass")) {
196 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
197 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
200 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
201 if (_strictIdentEnabled) {
205 if (Quassel::isOptionSet("oidentd")) {
206 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
210 if (Quassel::isOptionSet("ident-daemon")) {
211 _identServer = new IdentServer(this);
214 Quassel::registerReloadHandler([]() {
215 // Currently, only reloading SSL certificates and the sysident cache is supported
216 if (Core::instance()) {
217 Core::instance()->cacheSysIdent();
218 Core::instance()->reloadCerts();
224 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
225 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
228 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
229 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
231 if (!startListening()) {
232 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
235 if (_configured && !Quassel::isOptionSet("norestore")) {
236 Core::restoreState();
241 if (_pendingInternalConnection) {
242 connectInternalPeer(_pendingInternalConnection);
243 _pendingInternalConnection = {};
248 void Core::initAsync()
253 catch (ExitException e) {
254 emit exitRequested(e.exitCode, e.errorString);
259 void Core::shutdown()
261 quInfo() << "Core shutting down...";
265 for (auto &&client : _connectingClients) {
266 client->deleteLater();
268 _connectingClients.clear();
270 if (_sessions.isEmpty()) {
271 emit shutdownComplete();
275 for (auto &&session : _sessions) {
276 connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
282 void Core::onSessionShutdown(SessionThread *session)
284 _sessions.take(_sessions.key(session))->deleteLater();
285 if (_sessions.isEmpty()) {
286 quInfo() << "Core shutdown complete!";
287 emit shutdownComplete();
292 /*** Session Restore ***/
294 void Core::saveState()
297 QVariantList activeSessions;
298 for (auto &&user : instance()->_sessions.keys())
299 activeSessions << QVariant::fromValue<UserId>(user);
300 _storage->setCoreState(activeSessions);
305 void Core::restoreState()
308 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
311 if (_sessions.count()) {
312 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
317 /* We don't check, since we are at the first version since switching to Git
318 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
320 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
325 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
326 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
328 if (activeSessions.count() > 0) {
329 quInfo() << "Restoring previous core state...";
330 for(auto &&v : activeSessions) {
331 UserId user = v.value<UserId>();
332 sessionForUser(user, true);
340 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
342 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
346 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
349 return tr("Core is already configured! Not configuring again...");
351 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
352 return tr("Admin user or password not set.");
355 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
356 return tr("Could not setup storage!");
359 quInfo() << "Selected authenticator:" << authenticator;
360 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
362 return tr("Could not setup authenticator!");
365 catch (ExitException e) {
366 // Event loop is running, so trigger an exit rather than throwing an exception
367 QCoreApplication::exit(e.exitCode);
368 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
371 if (!saveBackendSettings(backend, setupData)) {
372 return tr("Could not save backend settings, probably a permission problem.");
374 saveAuthenticatorSettings(authenticator, authSetupData);
376 quInfo() << qPrintable(tr("Creating admin user..."));
377 _storage->addUser(adminUser, adminPassword);
379 startListening(); // TODO check when we need this
384 QString Core::setupCoreForInternalUsage()
386 Q_ASSERT(!_registeredStorageBackends.empty());
388 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
390 for (int i = 0; i < 10; i++) {
392 pass += qrand() % 10;
395 // mono client currently needs sqlite
396 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
400 /*** Storage Handling ***/
402 template<typename Storage>
403 void Core::registerStorageBackend()
405 auto backend = makeDeferredShared<Storage>(this);
406 if (backend->isAvailable())
407 _registeredStorageBackends.emplace_back(std::move(backend));
409 backend->deleteLater();
413 void Core::registerStorageBackends()
415 if (_registeredStorageBackends.empty()) {
416 registerStorageBackend<SqliteStorage>();
417 registerStorageBackend<PostgreSqlStorage>();
422 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
424 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
425 [backendId](const DeferredSharedPtr<Storage> &backend) {
426 return backend->displayName() == backendId;
428 return it != _registeredStorageBackends.end() ? *it : nullptr;
432 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
433 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
435 if (backend.isEmpty()) {
436 quWarning() << "No storage backend selected!";
440 auto storage = storageBackend(backend);
442 qCritical() << "Selected storage backend is not available:" << backend;
446 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
448 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
449 switch (storageState) {
450 case Storage::NeedsSetup:
452 return false; // trigger setup process
453 if (storage->setup(settings, environment, loadFromEnvironment))
454 return initStorage(backend, settings, environment, loadFromEnvironment, false);
457 case Storage::NotAvailable:
459 // If initialization wasn't successful, we quit to keep from coming up unconfigured
460 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
462 qCritical() << "Selected storage backend is not available:" << backend;
465 case Storage::IsReady:
466 // delete all other backends
467 _registeredStorageBackends.clear();
468 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
469 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
472 _storage = std::move(storage);
477 void Core::syncStorage()
484 /*** Storage Access ***/
485 bool Core::createNetwork(UserId user, NetworkInfo &info)
487 NetworkId networkId = instance()->_storage->createNetwork(user, info);
488 if (!networkId.isValid())
491 info.networkId = networkId;
496 /*** Authenticators ***/
498 // Authentication handling, now independent from storage.
499 template<typename Authenticator>
500 void Core::registerAuthenticator()
502 auto authenticator = makeDeferredShared<Authenticator>(this);
503 if (authenticator->isAvailable())
504 _registeredAuthenticators.emplace_back(std::move(authenticator));
506 authenticator->deleteLater();
510 void Core::registerAuthenticators()
512 if (_registeredAuthenticators.empty()) {
513 registerAuthenticator<SqlAuthenticator>();
515 registerAuthenticator<LdapAuthenticator>();
521 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
523 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
524 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
525 return authenticator->backendId() == backendId;
527 return it != _registeredAuthenticators.end() ? *it : nullptr;
531 // FIXME: Apparently, this is the legacy way of initting storage backends?
532 // If there's a not-legacy way, it should be used here
533 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
534 const QProcessEnvironment &environment, bool loadFromEnvironment,
537 if (backend.isEmpty()) {
538 quWarning() << "No authenticator selected!";
542 auto auth = authenticator(backend);
544 qCritical() << "Selected auth backend is not available:" << backend;
548 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
550 case Authenticator::NeedsSetup:
552 return false; // trigger setup process
553 if (auth->setup(settings, environment, loadFromEnvironment))
554 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
557 case Authenticator::NotAvailable:
559 // If initialization wasn't successful, we quit to keep from coming up unconfigured
560 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
562 qCritical() << "Selected auth backend is not available:" << backend;
565 case Authenticator::IsReady:
566 // delete all other backends
567 _registeredAuthenticators.clear();
570 _authenticator = std::move(auth);
575 /*** Network Management ***/
577 bool Core::sslSupported()
580 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
581 return sslServer && sslServer->isCertValid();
588 bool Core::reloadCerts()
591 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
592 bool retv4 = sslServerv4->reloadCerts();
594 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
595 bool retv6 = sslServerv6->reloadCerts();
597 return retv4 && retv6;
599 // SSL not supported, don't mark configuration reload as failed
605 void Core::cacheSysIdent()
607 if (isConfigured()) {
608 _authUserNames = _storage->getAllAuthUserNames();
613 QString Core::strictSysIdent(UserId user) const
615 if (_authUserNames.contains(user)) {
616 return _authUserNames[user];
619 // A new user got added since we last pulled our cache from the database.
620 // There's no way to avoid a database hit - we don't even know the authname!
621 instance()->cacheSysIdent();
623 if (_authUserNames.contains(user)) {
624 return _authUserNames[user];
627 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
628 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
629 return "unknown"; // Should we just terminate the program instead?
633 bool Core::startListening()
635 // in mono mode we only start a local port if a port is specified in the cli call
636 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
639 bool success = false;
640 uint port = Quassel::optionValue("port").toUInt();
642 const QString listen = Quassel::optionValue("listen");
643 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
644 if (listen_list.size() > 0) {
645 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
647 if (!addr.setAddress(listen_term)) {
648 qCritical() << qPrintable(
649 tr("Invalid listen address %1")
654 switch (addr.protocol()) {
655 case QAbstractSocket::IPv6Protocol:
656 if (_v6server.listen(addr, port)) {
657 quInfo() << qPrintable(
658 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
659 .arg(addr.toString())
660 .arg(_v6server.serverPort())
661 .arg(Quassel::buildInfo().protocolVersion)
666 quWarning() << qPrintable(
667 tr("Could not open IPv6 interface %1:%2: %3")
668 .arg(addr.toString())
670 .arg(_v6server.errorString()));
672 case QAbstractSocket::IPv4Protocol:
673 if (_server.listen(addr, port)) {
674 quInfo() << qPrintable(
675 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
676 .arg(addr.toString())
677 .arg(_server.serverPort())
678 .arg(Quassel::buildInfo().protocolVersion)
683 // if v6 succeeded on Any, the port will be already in use - don't display the error then
684 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
685 quWarning() << qPrintable(
686 tr("Could not open IPv4 interface %1:%2: %3")
687 .arg(addr.toString())
689 .arg(_server.errorString()));
693 qCritical() << qPrintable(
694 tr("Invalid listen address %1, unknown network protocol")
703 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
706 _identServer->startListening();
713 void Core::stopListening(const QString &reason)
716 _identServer->stopListening(reason);
719 bool wasListening = false;
720 if (_server.isListening()) {
724 if (_v6server.isListening()) {
729 if (reason.isEmpty())
730 quInfo() << "No longer listening for GUI clients.";
732 quInfo() << qPrintable(reason);
737 void Core::incomingConnection()
739 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
741 while (server->hasPendingConnections()) {
742 QTcpSocket *socket = server->nextPendingConnection();
744 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
745 _connectingClients.insert(handler);
747 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
748 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
749 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
751 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
754 stopListening(tr("Closing server for basic setup."));
760 // Potentially called during the initialization phase (before handing the connection off to the session)
761 void Core::clientDisconnected()
763 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
766 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
767 _connectingClients.remove(handler);
768 handler->deleteLater();
770 // make server listen again if still not configured
775 // TODO remove unneeded sessions - if necessary/possible...
776 // Suggestion: kill sessions if they are not connected to any network and client.
780 void Core::setupClientSession(RemotePeer *peer, UserId uid)
782 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
785 // From now on everything is handled by the client session
786 disconnect(handler, 0, this, 0);
787 _connectingClients.remove(handler);
788 handler->deleteLater();
790 // Find or create session for validated user
793 // as we are currently handling an event triggered by incoming data on this socket
794 // it is unsafe to directly move the socket to the client thread.
795 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
799 void Core::customEvent(QEvent *event)
801 if (event->type() == AddClientEventId) {
802 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
803 addClientHelper(addClientEvent->peer, addClientEvent->userId);
809 void Core::addClientHelper(RemotePeer *peer, UserId uid)
811 // Find or create session for validated user
812 SessionThread *session = sessionForUser(uid);
813 session->addClient(peer);
817 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
819 if (_initialized && peer) {
820 setupInternalClientSession(peer);
823 _pendingInternalConnection = peer;
828 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
832 auto errorString = setupCoreForInternalUsage();
833 if (!errorString.isEmpty()) {
834 emit exitRequested(EXIT_FAILURE, errorString);
841 uid = _storage->internalUser();
844 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
845 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
850 quWarning() << "Client peer went away, not starting a session";
854 InternalPeer *corePeer = new InternalPeer(this);
855 corePeer->setPeer(clientPeer);
856 clientPeer->setPeer(corePeer);
858 // Find or create session for validated user
859 SessionThread *sessionThread = sessionForUser(uid);
860 sessionThread->addClient(corePeer);
864 SessionThread *Core::sessionForUser(UserId uid, bool restore)
866 if (_sessions.contains(uid))
867 return _sessions[uid];
869 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
873 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
875 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
879 QVariantList Core::backendInfo()
881 instance()->registerStorageBackends();
883 QVariantList backendInfos;
884 for (auto &&backend : instance()->_registeredStorageBackends) {
886 v["BackendId"] = backend->backendId();
887 v["DisplayName"] = backend->displayName();
888 v["Description"] = backend->description();
889 v["SetupData"] = backend->setupData(); // ignored by legacy clients
891 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
892 const auto &setupData = backend->setupData();
893 QStringList setupKeys;
894 QVariantMap setupDefaults;
895 for (int i = 0; i + 2 < setupData.size(); i += 3) {
896 setupKeys << setupData[i].toString();
897 setupDefaults[setupData[i].toString()] = setupData[i + 2];
899 v["SetupKeys"] = setupKeys;
900 v["SetupDefaults"] = setupDefaults;
901 // TODO Protocol Break: Remove
902 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
910 QVariantList Core::authenticatorInfo()
912 instance()->registerAuthenticators();
914 QVariantList authInfos;
915 for(auto &&backend : instance()->_registeredAuthenticators) {
917 v["BackendId"] = backend->backendId();
918 v["DisplayName"] = backend->displayName();
919 v["Description"] = backend->description();
920 v["SetupData"] = backend->setupData();
926 // migration / backend selection
927 bool Core::selectBackend(const QString &backend)
929 // reregister all storage backends
930 registerStorageBackends();
931 auto storage = storageBackend(backend);
933 QStringList backends;
934 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
935 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
936 return backend->displayName();
938 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
939 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
943 QVariantMap settings = promptForSettings(storage.get());
945 Storage::State storageState = storage->init(settings);
946 switch (storageState) {
947 case Storage::IsReady:
948 if (!saveBackendSettings(backend, settings)) {
949 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
951 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
952 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
954 case Storage::NotAvailable:
955 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
957 case Storage::NeedsSetup:
958 if (!storage->setup(settings)) {
959 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
963 if (storage->init(settings) != Storage::IsReady) {
964 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
968 if (!saveBackendSettings(backend, settings)) {
969 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
971 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
975 // let's see if we have a current storage object we can migrate from
976 auto reader = getMigrationReader(_storage.get());
977 auto writer = getMigrationWriter(storage.get());
978 if (reader && writer) {
979 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
982 if (reader->migrateTo(writer.get())) {
983 qDebug() << "Migration finished!";
984 qDebug() << qPrintable(tr("Migration finished!"));
985 if (!saveBackendSettings(backend, settings)) {
986 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
991 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
995 // inform the user why we cannot merge
997 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
1000 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
1003 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
1006 // so we were unable to merge, but let's create a user \o/
1007 _storage = std::move(storage);
1012 // TODO: I am not sure if this function is implemented correctly.
1013 // There is currently no concept of migraiton between auth backends.
1014 bool Core::selectAuthenticator(const QString &backend)
1016 // Register all authentication backends.
1017 registerAuthenticators();
1018 auto auther = authenticator(backend);
1020 QStringList authenticators;
1021 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1022 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1023 return authenticator->displayName();
1025 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1026 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1030 QVariantMap settings = promptForSettings(auther.get());
1032 Authenticator::State state = auther->init(settings);
1034 case Authenticator::IsReady:
1035 saveAuthenticatorSettings(backend, settings);
1036 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1038 case Authenticator::NotAvailable:
1039 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1041 case Authenticator::NeedsSetup:
1042 if (!auther->setup(settings)) {
1043 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1047 if (auther->init(settings) != Authenticator::IsReady) {
1048 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1052 saveAuthenticatorSettings(backend, settings);
1053 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1056 _authenticator = std::move(auther);
1061 bool Core::createUser()
1063 QTextStream out(stdout);
1064 QTextStream in(stdin);
1065 out << "Add a new user:" << endl;
1066 out << "Username: ";
1068 QString username = in.readLine().trimmed();
1071 out << "Password: ";
1073 QString password = in.readLine().trimmed();
1075 out << "Repeat Password: ";
1077 QString password2 = in.readLine().trimmed();
1081 if (password != password2) {
1082 quWarning() << "Passwords don't match!";
1085 if (password.isEmpty()) {
1086 quWarning() << "Password is empty!";
1090 if (_configured && _storage->addUser(username, password).isValid()) {
1091 out << "Added user " << username << " successfully!" << endl;
1095 quWarning() << "Unable to add user:" << qPrintable(username);
1101 bool Core::changeUserPass(const QString &username)
1103 QTextStream out(stdout);
1104 QTextStream in(stdin);
1105 UserId userId = _storage->getUserId(username);
1106 if (!userId.isValid()) {
1107 out << "User " << username << " does not exist." << endl;
1111 if (!canChangeUserPassword(userId)) {
1112 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1116 out << "Change password for user: " << username << endl;
1119 out << "New Password: ";
1121 QString password = in.readLine().trimmed();
1123 out << "Repeat Password: ";
1125 QString password2 = in.readLine().trimmed();
1129 if (password != password2) {
1130 quWarning() << "Passwords don't match!";
1133 if (password.isEmpty()) {
1134 quWarning() << "Password is empty!";
1138 if (_configured && _storage->updateUser(userId, password)) {
1139 out << "Password changed successfully!" << endl;
1143 quWarning() << "Failed to change password!";
1149 bool Core::changeUserPassword(UserId userId, const QString &password)
1151 if (!isConfigured() || !userId.isValid())
1154 if (!canChangeUserPassword(userId))
1157 return instance()->_storage->updateUser(userId, password);
1160 // TODO: this code isn't currently 100% optimal because the core
1161 // doesn't know it can have multiple auth providers configured (there aren't
1162 // multiple auth providers at the moment anyway) and we have hardcoded the
1163 // Database provider to be always allowed.
1164 bool Core::canChangeUserPassword(UserId userId)
1166 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1167 if (authProvider != "Database") {
1168 if (authProvider != instance()->_authenticator->backendId()) {
1171 else if (instance()->_authenticator->canChangePassword()) {
1179 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1184 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1186 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1190 return sqlStorage->createMigrationReader();
1194 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1199 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1201 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1205 return sqlStorage->createMigrationWriter();
1209 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1211 QVariantMap dbsettings;
1212 dbsettings["Backend"] = backend;
1213 dbsettings["ConnectionProperties"] = settings;
1214 CoreSettings s = CoreSettings();
1215 s.setStorageSettings(dbsettings);
1220 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1222 QVariantMap dbsettings;
1223 dbsettings["Authenticator"] = backend;
1224 dbsettings["AuthProperties"] = settings;
1225 CoreSettings().setAuthSettings(dbsettings);
1228 // Generic version of promptForSettings that doesn't care what *type* of
1229 // backend it runs over.
1230 template<typename Backend>
1231 QVariantMap Core::promptForSettings(const Backend *backend)
1233 QVariantMap settings;
1234 const QVariantList& setupData = backend->setupData();
1236 if (setupData.isEmpty())
1239 QTextStream out(stdout);
1240 QTextStream in(stdin);
1241 out << "Default values are in brackets" << endl;
1243 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1244 QString key = setupData[i].toString();
1245 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1247 bool noEcho = key.toLower().contains("password");
1251 QString input = in.readLine().trimmed();
1257 QVariant value{setupData[i+2]};
1258 if (!input.isEmpty()) {
1259 switch (value.type()) {
1261 value = input.toInt();
1267 settings[key] = value;
1274 void Core::stdInEcho(bool on)
1276 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1278 GetConsoleMode(hStdin, &mode);
1280 mode |= ENABLE_ECHO_INPUT;
1282 mode &= ~ENABLE_ECHO_INPUT;
1283 SetConsoleMode(hStdin, mode);
1287 void Core::stdInEcho(bool on)
1290 tcgetattr(STDIN_FILENO, &t);
1295 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1298 #endif /* Q_OS_WIN */