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 // ==============================
70 Core *Core::_instance{nullptr};
72 Core *Core::instance()
81 qWarning() << "Recreating core instance!";
86 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
87 // thread, they get moved with it
88 _server.setParent(this);
89 _v6server.setParent(this);
90 _storageSyncTimer.setParent(this);
97 qDeleteAll(_connectingClients);
98 qDeleteAll(_sessions);
106 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
108 if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
109 Quassel::loadTranslation(QLocale::system());
112 // check settings version
113 // so far, we only have 1
115 if (s.version() != 1) {
116 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
119 // Set up storage and authentication backends
120 registerStorageBackends();
121 registerAuthenticators();
123 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
124 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
127 QVariantMap db_connectionProperties;
129 QString auth_authenticator;
130 QVariantMap auth_properties;
132 bool writeError = false;
134 if (config_from_environment) {
135 db_backend = environment.value("DB_BACKEND");
136 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
141 QVariantMap dbsettings = cs.storageSettings().toMap();
142 db_backend = dbsettings.value("Backend").toString();
143 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
145 QVariantMap authSettings = cs.authSettings().toMap();
146 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
147 auth_properties = authSettings.value("AuthProperties").toMap();
149 writeError = !cs.isWritable();
153 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
155 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
158 catch (ExitException) {
163 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
165 if (Quassel::isOptionSet("select-backend")) {
166 success &= selectBackend(Quassel::optionValue("select-backend"));
168 if (Quassel::isOptionSet("select-authenticator")) {
169 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
171 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
175 if (config_from_environment) {
177 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
179 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
182 catch (ExitException e) {
183 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
187 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
191 if (_registeredStorageBackends.empty()) {
192 throw ExitException{EXIT_FAILURE,
193 tr("Could not initialize any storage backend! Exiting...\n"
194 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
195 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
200 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
203 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
207 if (Quassel::isOptionSet("add-user")) {
208 bool success = createUser();
209 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
212 if (Quassel::isOptionSet("change-userpass")) {
213 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
214 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
217 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
218 if (_strictIdentEnabled) {
222 if (Quassel::isOptionSet("oidentd")) {
223 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
227 if (Quassel::isOptionSet("ident-daemon")) {
228 _identServer = new IdentServer(this);
231 Quassel::registerReloadHandler([]() {
232 // Currently, only reloading SSL certificates and the sysident cache is supported
233 if (Core::instance()) {
234 Core::instance()->cacheSysIdent();
235 Core::instance()->reloadCerts();
241 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
242 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
245 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
246 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
248 if (!startListening()) {
249 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
252 if (_configured && !Quassel::isOptionSet("norestore")) {
253 Core::restoreState();
258 if (_pendingInternalConnection) {
259 connectInternalPeer(_pendingInternalConnection);
260 _pendingInternalConnection = {};
265 void Core::initAsync()
270 catch (ExitException e) {
271 emit exitRequested(e.exitCode, e.errorString);
276 /*** Session Restore ***/
278 void Core::saveState()
281 QVariantList activeSessions;
282 for (auto &&user : instance()->_sessions.keys())
283 activeSessions << QVariant::fromValue<UserId>(user);
284 _storage->setCoreState(activeSessions);
289 void Core::restoreState()
292 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
295 if (_sessions.count()) {
296 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
301 /* We don't check, since we are at the first version since switching to Git
302 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
304 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
309 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
310 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
312 if (activeSessions.count() > 0) {
313 quInfo() << "Restoring previous core state...";
314 for(auto &&v : activeSessions) {
315 UserId user = v.value<UserId>();
316 sessionForUser(user, true);
324 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
326 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
330 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
333 return tr("Core is already configured! Not configuring again...");
335 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
336 return tr("Admin user or password not set.");
339 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
340 return tr("Could not setup storage!");
343 quInfo() << "Selected authenticator:" << authenticator;
344 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
346 return tr("Could not setup authenticator!");
349 catch (ExitException e) {
350 // Event loop is running, so trigger an exit rather than throwing an exception
351 QCoreApplication::exit(e.exitCode);
352 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
355 if (!saveBackendSettings(backend, setupData)) {
356 return tr("Could not save backend settings, probably a permission problem.");
358 saveAuthenticatorSettings(authenticator, authSetupData);
360 quInfo() << qPrintable(tr("Creating admin user..."));
361 _storage->addUser(adminUser, adminPassword);
363 startListening(); // TODO check when we need this
368 QString Core::setupCoreForInternalUsage()
370 Q_ASSERT(!_registeredStorageBackends.empty());
372 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
374 for (int i = 0; i < 10; i++) {
376 pass += qrand() % 10;
379 // mono client currently needs sqlite
380 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
384 /*** Storage Handling ***/
386 template<typename Storage>
387 void Core::registerStorageBackend()
389 auto backend = makeDeferredShared<Storage>(this);
390 if (backend->isAvailable())
391 _registeredStorageBackends.emplace_back(std::move(backend));
393 backend->deleteLater();
397 void Core::registerStorageBackends()
399 if (_registeredStorageBackends.empty()) {
400 registerStorageBackend<SqliteStorage>();
401 registerStorageBackend<PostgreSqlStorage>();
406 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
408 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
409 [backendId](const DeferredSharedPtr<Storage> &backend) {
410 return backend->displayName() == backendId;
412 return it != _registeredStorageBackends.end() ? *it : nullptr;
416 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
417 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
419 if (backend.isEmpty()) {
420 quWarning() << "No storage backend selected!";
424 auto storage = storageBackend(backend);
426 qCritical() << "Selected storage backend is not available:" << backend;
430 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
432 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
433 switch (storageState) {
434 case Storage::NeedsSetup:
436 return false; // trigger setup process
437 if (storage->setup(settings, environment, loadFromEnvironment))
438 return initStorage(backend, settings, environment, loadFromEnvironment, false);
441 case Storage::NotAvailable:
443 // If initialization wasn't successful, we quit to keep from coming up unconfigured
444 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
446 qCritical() << "Selected storage backend is not available:" << backend;
449 case Storage::IsReady:
450 // delete all other backends
451 _registeredStorageBackends.clear();
452 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
453 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
456 _storage = std::move(storage);
461 void Core::syncStorage()
468 /*** Storage Access ***/
469 bool Core::createNetwork(UserId user, NetworkInfo &info)
471 NetworkId networkId = instance()->_storage->createNetwork(user, info);
472 if (!networkId.isValid())
475 info.networkId = networkId;
480 /*** Authenticators ***/
482 // Authentication handling, now independent from storage.
483 template<typename Authenticator>
484 void Core::registerAuthenticator()
486 auto authenticator = makeDeferredShared<Authenticator>(this);
487 if (authenticator->isAvailable())
488 _registeredAuthenticators.emplace_back(std::move(authenticator));
490 authenticator->deleteLater();
494 void Core::registerAuthenticators()
496 if (_registeredAuthenticators.empty()) {
497 registerAuthenticator<SqlAuthenticator>();
499 registerAuthenticator<LdapAuthenticator>();
505 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
507 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
508 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
509 return authenticator->backendId() == backendId;
511 return it != _registeredAuthenticators.end() ? *it : nullptr;
515 // FIXME: Apparently, this is the legacy way of initting storage backends?
516 // If there's a not-legacy way, it should be used here
517 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
518 const QProcessEnvironment &environment, bool loadFromEnvironment,
521 if (backend.isEmpty()) {
522 quWarning() << "No authenticator selected!";
526 auto auth = authenticator(backend);
528 qCritical() << "Selected auth backend is not available:" << backend;
532 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
534 case Authenticator::NeedsSetup:
536 return false; // trigger setup process
537 if (auth->setup(settings, environment, loadFromEnvironment))
538 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
541 case Authenticator::NotAvailable:
543 // If initialization wasn't successful, we quit to keep from coming up unconfigured
544 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
546 qCritical() << "Selected auth backend is not available:" << backend;
549 case Authenticator::IsReady:
550 // delete all other backends
551 _registeredAuthenticators.clear();
554 _authenticator = std::move(auth);
559 /*** Network Management ***/
561 bool Core::sslSupported()
564 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
565 return sslServer && sslServer->isCertValid();
572 bool Core::reloadCerts()
575 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
576 bool retv4 = sslServerv4->reloadCerts();
578 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
579 bool retv6 = sslServerv6->reloadCerts();
581 return retv4 && retv6;
583 // SSL not supported, don't mark configuration reload as failed
589 void Core::cacheSysIdent()
591 if (isConfigured()) {
592 _authUserNames = _storage->getAllAuthUserNames();
597 QString Core::strictSysIdent(UserId user) const
599 if (_authUserNames.contains(user)) {
600 return _authUserNames[user];
603 // A new user got added since we last pulled our cache from the database.
604 // There's no way to avoid a database hit - we don't even know the authname!
605 instance()->cacheSysIdent();
607 if (_authUserNames.contains(user)) {
608 return _authUserNames[user];
611 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
612 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
613 return "unknown"; // Should we just terminate the program instead?
617 bool Core::startListening()
619 // in mono mode we only start a local port if a port is specified in the cli call
620 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
623 bool success = false;
624 uint port = Quassel::optionValue("port").toUInt();
626 const QString listen = Quassel::optionValue("listen");
627 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
628 if (listen_list.size() > 0) {
629 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
631 if (!addr.setAddress(listen_term)) {
632 qCritical() << qPrintable(
633 tr("Invalid listen address %1")
638 switch (addr.protocol()) {
639 case QAbstractSocket::IPv6Protocol:
640 if (_v6server.listen(addr, port)) {
641 quInfo() << qPrintable(
642 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
643 .arg(addr.toString())
644 .arg(_v6server.serverPort())
645 .arg(Quassel::buildInfo().protocolVersion)
650 quWarning() << qPrintable(
651 tr("Could not open IPv6 interface %1:%2: %3")
652 .arg(addr.toString())
654 .arg(_v6server.errorString()));
656 case QAbstractSocket::IPv4Protocol:
657 if (_server.listen(addr, port)) {
658 quInfo() << qPrintable(
659 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
660 .arg(addr.toString())
661 .arg(_server.serverPort())
662 .arg(Quassel::buildInfo().protocolVersion)
667 // if v6 succeeded on Any, the port will be already in use - don't display the error then
668 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
669 quWarning() << qPrintable(
670 tr("Could not open IPv4 interface %1:%2: %3")
671 .arg(addr.toString())
673 .arg(_server.errorString()));
677 qCritical() << qPrintable(
678 tr("Invalid listen address %1, unknown network protocol")
687 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
690 _identServer->startListening();
697 void Core::stopListening(const QString &reason)
700 _identServer->stopListening(reason);
703 bool wasListening = false;
704 if (_server.isListening()) {
708 if (_v6server.isListening()) {
713 if (reason.isEmpty())
714 quInfo() << "No longer listening for GUI clients.";
716 quInfo() << qPrintable(reason);
721 void Core::incomingConnection()
723 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
725 while (server->hasPendingConnections()) {
726 QTcpSocket *socket = server->nextPendingConnection();
728 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
729 _connectingClients.insert(handler);
731 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
732 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
733 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
735 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
738 stopListening(tr("Closing server for basic setup."));
744 // Potentially called during the initialization phase (before handing the connection off to the session)
745 void Core::clientDisconnected()
747 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
750 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
751 _connectingClients.remove(handler);
752 handler->deleteLater();
754 // make server listen again if still not configured
759 // TODO remove unneeded sessions - if necessary/possible...
760 // Suggestion: kill sessions if they are not connected to any network and client.
764 void Core::setupClientSession(RemotePeer *peer, UserId uid)
766 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
769 // From now on everything is handled by the client session
770 disconnect(handler, 0, this, 0);
771 _connectingClients.remove(handler);
772 handler->deleteLater();
774 // Find or create session for validated user
777 // as we are currently handling an event triggered by incoming data on this socket
778 // it is unsafe to directly move the socket to the client thread.
779 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
783 void Core::customEvent(QEvent *event)
785 if (event->type() == AddClientEventId) {
786 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
787 addClientHelper(addClientEvent->peer, addClientEvent->userId);
793 void Core::addClientHelper(RemotePeer *peer, UserId uid)
795 // Find or create session for validated user
796 SessionThread *session = sessionForUser(uid);
797 session->addClient(peer);
801 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
803 if (_initialized && peer) {
804 setupInternalClientSession(peer);
807 _pendingInternalConnection = peer;
812 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
816 auto errorString = setupCoreForInternalUsage();
817 if (!errorString.isEmpty()) {
818 emit exitRequested(EXIT_FAILURE, errorString);
825 uid = _storage->internalUser();
828 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
829 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
834 quWarning() << "Client peer went away, not starting a session";
838 InternalPeer *corePeer = new InternalPeer(this);
839 corePeer->setPeer(clientPeer);
840 clientPeer->setPeer(corePeer);
842 // Find or create session for validated user
843 SessionThread *sessionThread = sessionForUser(uid);
844 sessionThread->addClient(corePeer);
848 SessionThread *Core::sessionForUser(UserId uid, bool restore)
850 if (_sessions.contains(uid))
851 return _sessions[uid];
853 SessionThread *session = new SessionThread(uid, restore, strictIdentEnabled(), this);
854 _sessions[uid] = session;
860 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
862 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
866 QVariantList Core::backendInfo()
868 instance()->registerStorageBackends();
870 QVariantList backendInfos;
871 for (auto &&backend : instance()->_registeredStorageBackends) {
873 v["BackendId"] = backend->backendId();
874 v["DisplayName"] = backend->displayName();
875 v["Description"] = backend->description();
876 v["SetupData"] = backend->setupData(); // ignored by legacy clients
878 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
879 const auto &setupData = backend->setupData();
880 QStringList setupKeys;
881 QVariantMap setupDefaults;
882 for (int i = 0; i + 2 < setupData.size(); i += 3) {
883 setupKeys << setupData[i].toString();
884 setupDefaults[setupData[i].toString()] = setupData[i + 2];
886 v["SetupKeys"] = setupKeys;
887 v["SetupDefaults"] = setupDefaults;
888 // TODO Protocol Break: Remove
889 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
897 QVariantList Core::authenticatorInfo()
899 instance()->registerAuthenticators();
901 QVariantList authInfos;
902 for(auto &&backend : instance()->_registeredAuthenticators) {
904 v["BackendId"] = backend->backendId();
905 v["DisplayName"] = backend->displayName();
906 v["Description"] = backend->description();
907 v["SetupData"] = backend->setupData();
913 // migration / backend selection
914 bool Core::selectBackend(const QString &backend)
916 // reregister all storage backends
917 registerStorageBackends();
918 auto storage = storageBackend(backend);
920 QStringList backends;
921 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
922 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
923 return backend->displayName();
925 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
926 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
930 QVariantMap settings = promptForSettings(storage.get());
932 Storage::State storageState = storage->init(settings);
933 switch (storageState) {
934 case Storage::IsReady:
935 if (!saveBackendSettings(backend, settings)) {
936 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
938 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
939 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
941 case Storage::NotAvailable:
942 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
944 case Storage::NeedsSetup:
945 if (!storage->setup(settings)) {
946 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
950 if (storage->init(settings) != Storage::IsReady) {
951 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
955 if (!saveBackendSettings(backend, settings)) {
956 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
958 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
962 // let's see if we have a current storage object we can migrate from
963 auto reader = getMigrationReader(_storage.get());
964 auto writer = getMigrationWriter(storage.get());
965 if (reader && writer) {
966 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
969 if (reader->migrateTo(writer.get())) {
970 qDebug() << "Migration finished!";
971 qDebug() << qPrintable(tr("Migration finished!"));
972 if (!saveBackendSettings(backend, settings)) {
973 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
978 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
982 // inform the user why we cannot merge
984 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
987 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
990 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
993 // so we were unable to merge, but let's create a user \o/
994 _storage = std::move(storage);
999 // TODO: I am not sure if this function is implemented correctly.
1000 // There is currently no concept of migraiton between auth backends.
1001 bool Core::selectAuthenticator(const QString &backend)
1003 // Register all authentication backends.
1004 registerAuthenticators();
1005 auto auther = authenticator(backend);
1007 QStringList authenticators;
1008 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1009 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1010 return authenticator->displayName();
1012 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1013 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1017 QVariantMap settings = promptForSettings(auther.get());
1019 Authenticator::State state = auther->init(settings);
1021 case Authenticator::IsReady:
1022 saveAuthenticatorSettings(backend, settings);
1023 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1025 case Authenticator::NotAvailable:
1026 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1028 case Authenticator::NeedsSetup:
1029 if (!auther->setup(settings)) {
1030 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1034 if (auther->init(settings) != Authenticator::IsReady) {
1035 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1039 saveAuthenticatorSettings(backend, settings);
1040 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1043 _authenticator = std::move(auther);
1048 bool Core::createUser()
1050 QTextStream out(stdout);
1051 QTextStream in(stdin);
1052 out << "Add a new user:" << endl;
1053 out << "Username: ";
1055 QString username = in.readLine().trimmed();
1058 out << "Password: ";
1060 QString password = in.readLine().trimmed();
1062 out << "Repeat Password: ";
1064 QString password2 = in.readLine().trimmed();
1068 if (password != password2) {
1069 quWarning() << "Passwords don't match!";
1072 if (password.isEmpty()) {
1073 quWarning() << "Password is empty!";
1077 if (_configured && _storage->addUser(username, password).isValid()) {
1078 out << "Added user " << username << " successfully!" << endl;
1082 quWarning() << "Unable to add user:" << qPrintable(username);
1088 bool Core::changeUserPass(const QString &username)
1090 QTextStream out(stdout);
1091 QTextStream in(stdin);
1092 UserId userId = _storage->getUserId(username);
1093 if (!userId.isValid()) {
1094 out << "User " << username << " does not exist." << endl;
1098 if (!canChangeUserPassword(userId)) {
1099 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1103 out << "Change password for user: " << username << endl;
1106 out << "New Password: ";
1108 QString password = in.readLine().trimmed();
1110 out << "Repeat Password: ";
1112 QString password2 = in.readLine().trimmed();
1116 if (password != password2) {
1117 quWarning() << "Passwords don't match!";
1120 if (password.isEmpty()) {
1121 quWarning() << "Password is empty!";
1125 if (_configured && _storage->updateUser(userId, password)) {
1126 out << "Password changed successfully!" << endl;
1130 quWarning() << "Failed to change password!";
1136 bool Core::changeUserPassword(UserId userId, const QString &password)
1138 if (!isConfigured() || !userId.isValid())
1141 if (!canChangeUserPassword(userId))
1144 return instance()->_storage->updateUser(userId, password);
1147 // TODO: this code isn't currently 100% optimal because the core
1148 // doesn't know it can have multiple auth providers configured (there aren't
1149 // multiple auth providers at the moment anyway) and we have hardcoded the
1150 // Database provider to be always allowed.
1151 bool Core::canChangeUserPassword(UserId userId)
1153 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1154 if (authProvider != "Database") {
1155 if (authProvider != instance()->_authenticator->backendId()) {
1158 else if (instance()->_authenticator->canChangePassword()) {
1166 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1171 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1173 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1177 return sqlStorage->createMigrationReader();
1181 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1186 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1188 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1192 return sqlStorage->createMigrationWriter();
1196 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1198 QVariantMap dbsettings;
1199 dbsettings["Backend"] = backend;
1200 dbsettings["ConnectionProperties"] = settings;
1201 CoreSettings s = CoreSettings();
1202 s.setStorageSettings(dbsettings);
1207 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1209 QVariantMap dbsettings;
1210 dbsettings["Authenticator"] = backend;
1211 dbsettings["AuthProperties"] = settings;
1212 CoreSettings().setAuthSettings(dbsettings);
1215 // Generic version of promptForSettings that doesn't care what *type* of
1216 // backend it runs over.
1217 template<typename Backend>
1218 QVariantMap Core::promptForSettings(const Backend *backend)
1220 QVariantMap settings;
1221 const QVariantList& setupData = backend->setupData();
1223 if (setupData.isEmpty())
1226 QTextStream out(stdout);
1227 QTextStream in(stdin);
1228 out << "Default values are in brackets" << endl;
1230 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1231 QString key = setupData[i].toString();
1232 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1234 bool noEcho = key.toLower().contains("password");
1238 QString input = in.readLine().trimmed();
1244 QVariant value{setupData[i+2]};
1245 if (!input.isEmpty()) {
1246 switch (value.type()) {
1248 value = input.toInt();
1254 settings[key] = value;
1261 void Core::stdInEcho(bool on)
1263 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1265 GetConsoleMode(hStdin, &mode);
1267 mode |= ENABLE_ECHO_INPUT;
1269 mode &= ~ENABLE_ECHO_INPUT;
1270 SetConsoleMode(hStdin, mode);
1274 void Core::stdInEcho(bool on)
1277 tcgetattr(STDIN_FILENO, &t);
1282 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1285 #endif /* Q_OS_WIN */