1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
23 #include <QCoreApplication>
26 #include "coreauthhandler.h"
27 #include "coresession.h"
28 #include "coresettings.h"
29 #include "internalpeer.h"
30 #include "logmessage.h"
32 #include "postgresqlstorage.h"
34 #include "sqlauthenticator.h"
35 #include "sqlitestorage.h"
39 // Currently building with LDAP bindings is optional.
41 #include "ldapauthenticator.h"
53 // ==============================
55 // ==============================
56 const int Core::AddClientEventId = QEvent::registerEventType();
58 class AddClientEvent : public QEvent
61 AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
67 // ==============================
69 // ==============================
72 : Singleton<Core>{this}
74 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
75 // thread, they get moved with it
76 _server.setParent(this);
77 _v6server.setParent(this);
78 _storageSyncTimer.setParent(this);
85 qDeleteAll(_connectingClients);
86 qDeleteAll(_sessions);
93 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
95 if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
96 Quassel::loadTranslation(QLocale::system());
99 // check settings version
100 // so far, we only have 1
102 if (s.version() != 1) {
103 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
106 // Set up storage and authentication backends
107 registerStorageBackends();
108 registerAuthenticators();
110 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
111 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
114 QVariantMap db_connectionProperties;
116 QString auth_authenticator;
117 QVariantMap auth_properties;
119 bool writeError = false;
121 if (config_from_environment) {
122 db_backend = environment.value("DB_BACKEND");
123 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
128 QVariantMap dbsettings = cs.storageSettings().toMap();
129 db_backend = dbsettings.value("Backend").toString();
130 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
132 QVariantMap authSettings = cs.authSettings().toMap();
133 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
134 auth_properties = authSettings.value("AuthProperties").toMap();
136 writeError = !cs.isWritable();
140 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
142 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
145 catch (ExitException) {
150 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
152 if (Quassel::isOptionSet("select-backend")) {
153 success &= selectBackend(Quassel::optionValue("select-backend"));
155 if (Quassel::isOptionSet("select-authenticator")) {
156 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
158 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
162 if (config_from_environment) {
164 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
166 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
169 catch (ExitException e) {
170 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
174 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
178 if (_registeredStorageBackends.empty()) {
179 throw ExitException{EXIT_FAILURE,
180 tr("Could not initialize any storage backend! Exiting...\n"
181 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
182 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
187 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
190 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
194 if (Quassel::isOptionSet("add-user")) {
195 bool success = createUser();
196 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
199 if (Quassel::isOptionSet("change-userpass")) {
200 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
201 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
204 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
205 if (_strictIdentEnabled) {
209 if (Quassel::isOptionSet("oidentd")) {
210 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
214 if (Quassel::isOptionSet("ident-daemon")) {
215 _identServer = new IdentServer(this);
218 Quassel::registerReloadHandler([]() {
219 // Currently, only reloading SSL certificates and the sysident cache is supported
220 if (Core::instance()) {
221 Core::instance()->cacheSysIdent();
222 Core::instance()->reloadCerts();
228 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
229 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
232 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
233 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
235 if (!startListening()) {
236 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
239 if (_configured && !Quassel::isOptionSet("norestore")) {
240 Core::restoreState();
245 if (_pendingInternalConnection) {
246 connectInternalPeer(_pendingInternalConnection);
247 _pendingInternalConnection = {};
252 void Core::initAsync()
257 catch (ExitException e) {
258 emit exitRequested(e.exitCode, e.errorString);
263 /*** Session Restore ***/
265 void Core::saveState()
268 QVariantList activeSessions;
269 for (auto &&user : instance()->_sessions.keys())
270 activeSessions << QVariant::fromValue<UserId>(user);
271 _storage->setCoreState(activeSessions);
276 void Core::restoreState()
279 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
282 if (_sessions.count()) {
283 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
288 /* We don't check, since we are at the first version since switching to Git
289 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
291 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
296 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
297 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
299 if (activeSessions.count() > 0) {
300 quInfo() << "Restoring previous core state...";
301 for(auto &&v : activeSessions) {
302 UserId user = v.value<UserId>();
303 sessionForUser(user, true);
311 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
313 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
317 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
320 return tr("Core is already configured! Not configuring again...");
322 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
323 return tr("Admin user or password not set.");
326 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
327 return tr("Could not setup storage!");
330 quInfo() << "Selected authenticator:" << authenticator;
331 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
333 return tr("Could not setup authenticator!");
336 catch (ExitException e) {
337 // Event loop is running, so trigger an exit rather than throwing an exception
338 QCoreApplication::exit(e.exitCode);
339 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
342 if (!saveBackendSettings(backend, setupData)) {
343 return tr("Could not save backend settings, probably a permission problem.");
345 saveAuthenticatorSettings(authenticator, authSetupData);
347 quInfo() << qPrintable(tr("Creating admin user..."));
348 _storage->addUser(adminUser, adminPassword);
350 startListening(); // TODO check when we need this
355 QString Core::setupCoreForInternalUsage()
357 Q_ASSERT(!_registeredStorageBackends.empty());
359 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
361 for (int i = 0; i < 10; i++) {
363 pass += qrand() % 10;
366 // mono client currently needs sqlite
367 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
371 /*** Storage Handling ***/
373 template<typename Storage>
374 void Core::registerStorageBackend()
376 auto backend = makeDeferredShared<Storage>(this);
377 if (backend->isAvailable())
378 _registeredStorageBackends.emplace_back(std::move(backend));
380 backend->deleteLater();
384 void Core::registerStorageBackends()
386 if (_registeredStorageBackends.empty()) {
387 registerStorageBackend<SqliteStorage>();
388 registerStorageBackend<PostgreSqlStorage>();
393 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
395 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
396 [backendId](const DeferredSharedPtr<Storage> &backend) {
397 return backend->displayName() == backendId;
399 return it != _registeredStorageBackends.end() ? *it : nullptr;
403 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
404 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
406 if (backend.isEmpty()) {
407 quWarning() << "No storage backend selected!";
411 auto storage = storageBackend(backend);
413 qCritical() << "Selected storage backend is not available:" << backend;
417 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
419 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
420 switch (storageState) {
421 case Storage::NeedsSetup:
423 return false; // trigger setup process
424 if (storage->setup(settings, environment, loadFromEnvironment))
425 return initStorage(backend, settings, environment, loadFromEnvironment, false);
428 case Storage::NotAvailable:
430 // If initialization wasn't successful, we quit to keep from coming up unconfigured
431 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
433 qCritical() << "Selected storage backend is not available:" << backend;
436 case Storage::IsReady:
437 // delete all other backends
438 _registeredStorageBackends.clear();
439 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
440 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
443 _storage = std::move(storage);
448 void Core::syncStorage()
455 /*** Storage Access ***/
456 bool Core::createNetwork(UserId user, NetworkInfo &info)
458 NetworkId networkId = instance()->_storage->createNetwork(user, info);
459 if (!networkId.isValid())
462 info.networkId = networkId;
467 /*** Authenticators ***/
469 // Authentication handling, now independent from storage.
470 template<typename Authenticator>
471 void Core::registerAuthenticator()
473 auto authenticator = makeDeferredShared<Authenticator>(this);
474 if (authenticator->isAvailable())
475 _registeredAuthenticators.emplace_back(std::move(authenticator));
477 authenticator->deleteLater();
481 void Core::registerAuthenticators()
483 if (_registeredAuthenticators.empty()) {
484 registerAuthenticator<SqlAuthenticator>();
486 registerAuthenticator<LdapAuthenticator>();
492 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
494 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
495 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
496 return authenticator->backendId() == backendId;
498 return it != _registeredAuthenticators.end() ? *it : nullptr;
502 // FIXME: Apparently, this is the legacy way of initting storage backends?
503 // If there's a not-legacy way, it should be used here
504 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
505 const QProcessEnvironment &environment, bool loadFromEnvironment,
508 if (backend.isEmpty()) {
509 quWarning() << "No authenticator selected!";
513 auto auth = authenticator(backend);
515 qCritical() << "Selected auth backend is not available:" << backend;
519 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
521 case Authenticator::NeedsSetup:
523 return false; // trigger setup process
524 if (auth->setup(settings, environment, loadFromEnvironment))
525 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
528 case Authenticator::NotAvailable:
530 // If initialization wasn't successful, we quit to keep from coming up unconfigured
531 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
533 qCritical() << "Selected auth backend is not available:" << backend;
536 case Authenticator::IsReady:
537 // delete all other backends
538 _registeredAuthenticators.clear();
541 _authenticator = std::move(auth);
546 /*** Network Management ***/
548 bool Core::sslSupported()
551 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
552 return sslServer && sslServer->isCertValid();
559 bool Core::reloadCerts()
562 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
563 bool retv4 = sslServerv4->reloadCerts();
565 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
566 bool retv6 = sslServerv6->reloadCerts();
568 return retv4 && retv6;
570 // SSL not supported, don't mark configuration reload as failed
576 void Core::cacheSysIdent()
578 if (isConfigured()) {
579 _authUserNames = _storage->getAllAuthUserNames();
584 QString Core::strictSysIdent(UserId user) const
586 if (_authUserNames.contains(user)) {
587 return _authUserNames[user];
590 // A new user got added since we last pulled our cache from the database.
591 // There's no way to avoid a database hit - we don't even know the authname!
592 instance()->cacheSysIdent();
594 if (_authUserNames.contains(user)) {
595 return _authUserNames[user];
598 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
599 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
600 return "unknown"; // Should we just terminate the program instead?
604 bool Core::startListening()
606 // in mono mode we only start a local port if a port is specified in the cli call
607 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
610 bool success = false;
611 uint port = Quassel::optionValue("port").toUInt();
613 const QString listen = Quassel::optionValue("listen");
614 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
615 if (listen_list.size() > 0) {
616 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
618 if (!addr.setAddress(listen_term)) {
619 qCritical() << qPrintable(
620 tr("Invalid listen address %1")
625 switch (addr.protocol()) {
626 case QAbstractSocket::IPv6Protocol:
627 if (_v6server.listen(addr, port)) {
628 quInfo() << qPrintable(
629 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
630 .arg(addr.toString())
631 .arg(_v6server.serverPort())
632 .arg(Quassel::buildInfo().protocolVersion)
637 quWarning() << qPrintable(
638 tr("Could not open IPv6 interface %1:%2: %3")
639 .arg(addr.toString())
641 .arg(_v6server.errorString()));
643 case QAbstractSocket::IPv4Protocol:
644 if (_server.listen(addr, port)) {
645 quInfo() << qPrintable(
646 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
647 .arg(addr.toString())
648 .arg(_server.serverPort())
649 .arg(Quassel::buildInfo().protocolVersion)
654 // if v6 succeeded on Any, the port will be already in use - don't display the error then
655 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
656 quWarning() << qPrintable(
657 tr("Could not open IPv4 interface %1:%2: %3")
658 .arg(addr.toString())
660 .arg(_server.errorString()));
664 qCritical() << qPrintable(
665 tr("Invalid listen address %1, unknown network protocol")
674 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
677 _identServer->startListening();
684 void Core::stopListening(const QString &reason)
687 _identServer->stopListening(reason);
690 bool wasListening = false;
691 if (_server.isListening()) {
695 if (_v6server.isListening()) {
700 if (reason.isEmpty())
701 quInfo() << "No longer listening for GUI clients.";
703 quInfo() << qPrintable(reason);
708 void Core::incomingConnection()
710 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
712 while (server->hasPendingConnections()) {
713 QTcpSocket *socket = server->nextPendingConnection();
715 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
716 _connectingClients.insert(handler);
718 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
719 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
720 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
722 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
725 stopListening(tr("Closing server for basic setup."));
731 // Potentially called during the initialization phase (before handing the connection off to the session)
732 void Core::clientDisconnected()
734 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
737 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
738 _connectingClients.remove(handler);
739 handler->deleteLater();
741 // make server listen again if still not configured
746 // TODO remove unneeded sessions - if necessary/possible...
747 // Suggestion: kill sessions if they are not connected to any network and client.
751 void Core::setupClientSession(RemotePeer *peer, UserId uid)
753 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
756 // From now on everything is handled by the client session
757 disconnect(handler, 0, this, 0);
758 _connectingClients.remove(handler);
759 handler->deleteLater();
761 // Find or create session for validated user
764 // as we are currently handling an event triggered by incoming data on this socket
765 // it is unsafe to directly move the socket to the client thread.
766 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
770 void Core::customEvent(QEvent *event)
772 if (event->type() == AddClientEventId) {
773 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
774 addClientHelper(addClientEvent->peer, addClientEvent->userId);
780 void Core::addClientHelper(RemotePeer *peer, UserId uid)
782 // Find or create session for validated user
783 SessionThread *session = sessionForUser(uid);
784 session->addClient(peer);
788 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
790 if (_initialized && peer) {
791 setupInternalClientSession(peer);
794 _pendingInternalConnection = peer;
799 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
803 auto errorString = setupCoreForInternalUsage();
804 if (!errorString.isEmpty()) {
805 emit exitRequested(EXIT_FAILURE, errorString);
812 uid = _storage->internalUser();
815 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
816 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
821 quWarning() << "Client peer went away, not starting a session";
825 InternalPeer *corePeer = new InternalPeer(this);
826 corePeer->setPeer(clientPeer);
827 clientPeer->setPeer(corePeer);
829 // Find or create session for validated user
830 SessionThread *sessionThread = sessionForUser(uid);
831 sessionThread->addClient(corePeer);
835 SessionThread *Core::sessionForUser(UserId uid, bool restore)
837 if (_sessions.contains(uid))
838 return _sessions[uid];
840 SessionThread *session = new SessionThread(uid, restore, strictIdentEnabled(), this);
841 _sessions[uid] = session;
847 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
849 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
853 QVariantList Core::backendInfo()
855 instance()->registerStorageBackends();
857 QVariantList backendInfos;
858 for (auto &&backend : instance()->_registeredStorageBackends) {
860 v["BackendId"] = backend->backendId();
861 v["DisplayName"] = backend->displayName();
862 v["Description"] = backend->description();
863 v["SetupData"] = backend->setupData(); // ignored by legacy clients
865 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
866 const auto &setupData = backend->setupData();
867 QStringList setupKeys;
868 QVariantMap setupDefaults;
869 for (int i = 0; i + 2 < setupData.size(); i += 3) {
870 setupKeys << setupData[i].toString();
871 setupDefaults[setupData[i].toString()] = setupData[i + 2];
873 v["SetupKeys"] = setupKeys;
874 v["SetupDefaults"] = setupDefaults;
875 // TODO Protocol Break: Remove
876 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
884 QVariantList Core::authenticatorInfo()
886 instance()->registerAuthenticators();
888 QVariantList authInfos;
889 for(auto &&backend : instance()->_registeredAuthenticators) {
891 v["BackendId"] = backend->backendId();
892 v["DisplayName"] = backend->displayName();
893 v["Description"] = backend->description();
894 v["SetupData"] = backend->setupData();
900 // migration / backend selection
901 bool Core::selectBackend(const QString &backend)
903 // reregister all storage backends
904 registerStorageBackends();
905 auto storage = storageBackend(backend);
907 QStringList backends;
908 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
909 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
910 return backend->displayName();
912 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
913 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
917 QVariantMap settings = promptForSettings(storage.get());
919 Storage::State storageState = storage->init(settings);
920 switch (storageState) {
921 case Storage::IsReady:
922 if (!saveBackendSettings(backend, settings)) {
923 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
925 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
926 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
928 case Storage::NotAvailable:
929 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
931 case Storage::NeedsSetup:
932 if (!storage->setup(settings)) {
933 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
937 if (storage->init(settings) != Storage::IsReady) {
938 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
942 if (!saveBackendSettings(backend, settings)) {
943 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
945 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
949 // let's see if we have a current storage object we can migrate from
950 auto reader = getMigrationReader(_storage.get());
951 auto writer = getMigrationWriter(storage.get());
952 if (reader && writer) {
953 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
956 if (reader->migrateTo(writer.get())) {
957 qDebug() << "Migration finished!";
958 qDebug() << qPrintable(tr("Migration finished!"));
959 if (!saveBackendSettings(backend, settings)) {
960 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
965 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
969 // inform the user why we cannot merge
971 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
974 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
977 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
980 // so we were unable to merge, but let's create a user \o/
981 _storage = std::move(storage);
986 // TODO: I am not sure if this function is implemented correctly.
987 // There is currently no concept of migraiton between auth backends.
988 bool Core::selectAuthenticator(const QString &backend)
990 // Register all authentication backends.
991 registerAuthenticators();
992 auto auther = authenticator(backend);
994 QStringList authenticators;
995 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
996 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
997 return authenticator->displayName();
999 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1000 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1004 QVariantMap settings = promptForSettings(auther.get());
1006 Authenticator::State state = auther->init(settings);
1008 case Authenticator::IsReady:
1009 saveAuthenticatorSettings(backend, settings);
1010 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1012 case Authenticator::NotAvailable:
1013 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1015 case Authenticator::NeedsSetup:
1016 if (!auther->setup(settings)) {
1017 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1021 if (auther->init(settings) != Authenticator::IsReady) {
1022 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1026 saveAuthenticatorSettings(backend, settings);
1027 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1030 _authenticator = std::move(auther);
1035 bool Core::createUser()
1037 QTextStream out(stdout);
1038 QTextStream in(stdin);
1039 out << "Add a new user:" << endl;
1040 out << "Username: ";
1042 QString username = in.readLine().trimmed();
1045 out << "Password: ";
1047 QString password = in.readLine().trimmed();
1049 out << "Repeat Password: ";
1051 QString password2 = in.readLine().trimmed();
1055 if (password != password2) {
1056 quWarning() << "Passwords don't match!";
1059 if (password.isEmpty()) {
1060 quWarning() << "Password is empty!";
1064 if (_configured && _storage->addUser(username, password).isValid()) {
1065 out << "Added user " << username << " successfully!" << endl;
1069 quWarning() << "Unable to add user:" << qPrintable(username);
1075 bool Core::changeUserPass(const QString &username)
1077 QTextStream out(stdout);
1078 QTextStream in(stdin);
1079 UserId userId = _storage->getUserId(username);
1080 if (!userId.isValid()) {
1081 out << "User " << username << " does not exist." << endl;
1085 if (!canChangeUserPassword(userId)) {
1086 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1090 out << "Change password for user: " << username << endl;
1093 out << "New Password: ";
1095 QString password = in.readLine().trimmed();
1097 out << "Repeat Password: ";
1099 QString password2 = in.readLine().trimmed();
1103 if (password != password2) {
1104 quWarning() << "Passwords don't match!";
1107 if (password.isEmpty()) {
1108 quWarning() << "Password is empty!";
1112 if (_configured && _storage->updateUser(userId, password)) {
1113 out << "Password changed successfully!" << endl;
1117 quWarning() << "Failed to change password!";
1123 bool Core::changeUserPassword(UserId userId, const QString &password)
1125 if (!isConfigured() || !userId.isValid())
1128 if (!canChangeUserPassword(userId))
1131 return instance()->_storage->updateUser(userId, password);
1134 // TODO: this code isn't currently 100% optimal because the core
1135 // doesn't know it can have multiple auth providers configured (there aren't
1136 // multiple auth providers at the moment anyway) and we have hardcoded the
1137 // Database provider to be always allowed.
1138 bool Core::canChangeUserPassword(UserId userId)
1140 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1141 if (authProvider != "Database") {
1142 if (authProvider != instance()->_authenticator->backendId()) {
1145 else if (instance()->_authenticator->canChangePassword()) {
1153 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1158 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1160 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1164 return sqlStorage->createMigrationReader();
1168 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1173 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1175 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1179 return sqlStorage->createMigrationWriter();
1183 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1185 QVariantMap dbsettings;
1186 dbsettings["Backend"] = backend;
1187 dbsettings["ConnectionProperties"] = settings;
1188 CoreSettings s = CoreSettings();
1189 s.setStorageSettings(dbsettings);
1194 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1196 QVariantMap dbsettings;
1197 dbsettings["Authenticator"] = backend;
1198 dbsettings["AuthProperties"] = settings;
1199 CoreSettings().setAuthSettings(dbsettings);
1202 // Generic version of promptForSettings that doesn't care what *type* of
1203 // backend it runs over.
1204 template<typename Backend>
1205 QVariantMap Core::promptForSettings(const Backend *backend)
1207 QVariantMap settings;
1208 const QVariantList& setupData = backend->setupData();
1210 if (setupData.isEmpty())
1213 QTextStream out(stdout);
1214 QTextStream in(stdin);
1215 out << "Default values are in brackets" << endl;
1217 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1218 QString key = setupData[i].toString();
1219 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1221 bool noEcho = key.toLower().contains("password");
1225 QString input = in.readLine().trimmed();
1231 QVariant value{setupData[i+2]};
1232 if (!input.isEmpty()) {
1233 switch (value.type()) {
1235 value = input.toInt();
1241 settings[key] = value;
1248 void Core::stdInEcho(bool on)
1250 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1252 GetConsoleMode(hStdin, &mode);
1254 mode |= ENABLE_ECHO_INPUT;
1256 mode &= ~ENABLE_ECHO_INPUT;
1257 SetConsoleMode(hStdin, mode);
1261 void Core::stdInEcho(bool on)
1264 tcgetattr(STDIN_FILENO, &t);
1269 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1272 #endif /* Q_OS_WIN */