1 /***************************************************************************
2 * Copyright (C) 2005-2019 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
23 #include <QCoreApplication>
26 #include "coreauthhandler.h"
27 #include "coresession.h"
28 #include "coresettings.h"
29 #include "internalpeer.h"
30 #include "logmessage.h"
32 #include "postgresqlstorage.h"
34 #include "sqlauthenticator.h"
35 #include "sqlitestorage.h"
39 // Currently building with LDAP bindings is optional.
41 #include "ldapauthenticator.h"
53 // ==============================
55 // ==============================
56 const int Core::AddClientEventId = QEvent::registerEventType();
58 class AddClientEvent : public QEvent
61 AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
67 // ==============================
69 // ==============================
72 : Singleton<Core>{this}
74 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
75 // thread, they get moved with it
76 _server.setParent(this);
77 _v6server.setParent(this);
78 _storageSyncTimer.setParent(this);
84 qDeleteAll(_connectingClients);
85 qDeleteAll(_sessions);
92 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
94 if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
95 Quassel::loadTranslation(QLocale::system());
98 // check settings version
99 // so far, we only have 1
101 if (s.version() != 1) {
102 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
105 // Set up storage and authentication backends
106 registerStorageBackends();
107 registerAuthenticators();
109 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
110 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
113 QVariantMap db_connectionProperties;
115 QString auth_authenticator;
116 QVariantMap auth_properties;
118 bool writeError = false;
120 if (config_from_environment) {
121 db_backend = environment.value("DB_BACKEND");
122 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
127 QVariantMap dbsettings = cs.storageSettings().toMap();
128 db_backend = dbsettings.value("Backend").toString();
129 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
131 QVariantMap authSettings = cs.authSettings().toMap();
132 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
133 auth_properties = authSettings.value("AuthProperties").toMap();
135 writeError = !cs.isWritable();
139 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
141 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
144 catch (ExitException) {
149 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
151 if (Quassel::isOptionSet("select-backend")) {
152 success &= selectBackend(Quassel::optionValue("select-backend"));
154 if (Quassel::isOptionSet("select-authenticator")) {
155 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
157 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
161 if (config_from_environment) {
163 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
165 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
168 catch (ExitException e) {
169 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
173 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
177 if (_registeredStorageBackends.empty()) {
178 throw ExitException{EXIT_FAILURE,
179 tr("Could not initialize any storage backend! Exiting...\n"
180 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
181 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
186 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
189 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
193 // This checks separately because config-from-environment might have only configured the core just now
195 if (Quassel::isOptionSet("add-user")) {
196 bool success = createUser();
197 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
200 if (Quassel::isOptionSet("change-userpass")) {
201 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
202 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
205 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
206 if (_strictIdentEnabled) {
210 if (Quassel::isOptionSet("oidentd")) {
211 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
215 if (Quassel::isOptionSet("ident-daemon")) {
216 _identServer = new IdentServer(this);
219 Quassel::registerReloadHandler([]() {
220 // Currently, only reloading SSL certificates and the sysident cache is supported
221 if (Core::instance()) {
222 Core::instance()->cacheSysIdent();
223 Core::instance()->reloadCerts();
229 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
230 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
233 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
234 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
236 if (!startListening()) {
237 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
240 if (_configured && !Quassel::isOptionSet("norestore")) {
241 Core::restoreState();
246 if (_pendingInternalConnection) {
247 connectInternalPeer(_pendingInternalConnection);
248 _pendingInternalConnection = {};
253 void Core::initAsync()
258 catch (ExitException e) {
259 emit exitRequested(e.exitCode, e.errorString);
264 void Core::shutdown()
266 quInfo() << "Core shutting down...";
270 for (auto &&client : _connectingClients) {
271 client->deleteLater();
273 _connectingClients.clear();
275 if (_sessions.isEmpty()) {
276 emit shutdownComplete();
280 for (auto &&session : _sessions) {
281 connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
287 void Core::onSessionShutdown(SessionThread *session)
289 _sessions.take(_sessions.key(session))->deleteLater();
290 if (_sessions.isEmpty()) {
291 quInfo() << "Core shutdown complete!";
292 emit shutdownComplete();
297 /*** Session Restore ***/
299 void Core::saveState()
302 QVariantList activeSessions;
303 for (auto &&user : instance()->_sessions.keys())
304 activeSessions << QVariant::fromValue<UserId>(user);
305 _storage->setCoreState(activeSessions);
310 void Core::restoreState()
313 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
316 if (_sessions.count()) {
317 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
322 /* We don't check, since we are at the first version since switching to Git
323 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
325 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
330 const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
331 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
333 if (activeSessions.count() > 0) {
334 quInfo() << "Restoring previous core state...";
335 for(auto &&v : activeSessions) {
336 UserId user = v.value<UserId>();
337 sessionForUser(user, true);
345 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
347 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
351 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupData)
354 return tr("Core is already configured! Not configuring again...");
356 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
357 return tr("Admin user or password not set.");
360 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
361 return tr("Could not setup storage!");
364 quInfo() << "Selected authenticator:" << authenticator;
365 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
367 return tr("Could not setup authenticator!");
370 catch (ExitException e) {
371 // Event loop is running, so trigger an exit rather than throwing an exception
372 QCoreApplication::exit(e.exitCode);
373 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
376 if (!saveBackendSettings(backend, setupData)) {
377 return tr("Could not save backend settings, probably a permission problem.");
379 saveAuthenticatorSettings(authenticator, authSetupData);
381 quInfo() << qPrintable(tr("Creating admin user..."));
382 _storage->addUser(adminUser, adminPassword);
384 startListening(); // TODO check when we need this
389 QString Core::setupCoreForInternalUsage()
391 Q_ASSERT(!_registeredStorageBackends.empty());
393 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
395 for (int i = 0; i < 10; i++) {
397 pass += qrand() % 10;
400 // mono client currently needs sqlite
401 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
405 /*** Storage Handling ***/
407 template<typename Storage>
408 void Core::registerStorageBackend()
410 auto backend = makeDeferredShared<Storage>(this);
411 if (backend->isAvailable())
412 _registeredStorageBackends.emplace_back(std::move(backend));
414 backend->deleteLater();
418 void Core::registerStorageBackends()
420 if (_registeredStorageBackends.empty()) {
421 registerStorageBackend<SqliteStorage>();
422 registerStorageBackend<PostgreSqlStorage>();
427 DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
429 auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
430 [backendId](const DeferredSharedPtr<Storage> &backend) {
431 return backend->displayName() == backendId;
433 return it != _registeredStorageBackends.end() ? *it : nullptr;
437 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
438 const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
440 if (backend.isEmpty()) {
441 quWarning() << "No storage backend selected!";
445 auto storage = storageBackend(backend);
447 qCritical() << "Selected storage backend is not available:" << backend;
451 connect(storage.get(), SIGNAL(dbUpgradeInProgress(bool)), this, SIGNAL(dbUpgradeInProgress(bool)));
453 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
454 switch (storageState) {
455 case Storage::NeedsSetup:
457 return false; // trigger setup process
458 if (storage->setup(settings, environment, loadFromEnvironment))
459 return initStorage(backend, settings, environment, loadFromEnvironment, false);
462 case Storage::NotAvailable:
464 // If initialization wasn't successful, we quit to keep from coming up unconfigured
465 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
467 qCritical() << "Selected storage backend is not available:" << backend;
470 case Storage::IsReady:
471 // delete all other backends
472 _registeredStorageBackends.clear();
473 connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
474 this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
477 _storage = std::move(storage);
482 void Core::syncStorage()
489 /*** Storage Access ***/
490 bool Core::createNetwork(UserId user, NetworkInfo &info)
492 NetworkId networkId = instance()->_storage->createNetwork(user, info);
493 if (!networkId.isValid())
496 info.networkId = networkId;
501 /*** Authenticators ***/
503 // Authentication handling, now independent from storage.
504 template<typename Authenticator>
505 void Core::registerAuthenticator()
507 auto authenticator = makeDeferredShared<Authenticator>(this);
508 if (authenticator->isAvailable())
509 _registeredAuthenticators.emplace_back(std::move(authenticator));
511 authenticator->deleteLater();
515 void Core::registerAuthenticators()
517 if (_registeredAuthenticators.empty()) {
518 registerAuthenticator<SqlAuthenticator>();
520 registerAuthenticator<LdapAuthenticator>();
526 DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
528 auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
529 [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
530 return authenticator->backendId() == backendId;
532 return it != _registeredAuthenticators.end() ? *it : nullptr;
536 // FIXME: Apparently, this is the legacy way of initting storage backends?
537 // If there's a not-legacy way, it should be used here
538 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
539 const QProcessEnvironment &environment, bool loadFromEnvironment,
542 if (backend.isEmpty()) {
543 quWarning() << "No authenticator selected!";
547 auto auth = authenticator(backend);
549 qCritical() << "Selected auth backend is not available:" << backend;
553 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
555 case Authenticator::NeedsSetup:
557 return false; // trigger setup process
558 if (auth->setup(settings, environment, loadFromEnvironment))
559 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
562 case Authenticator::NotAvailable:
564 // If initialization wasn't successful, we quit to keep from coming up unconfigured
565 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
567 qCritical() << "Selected auth backend is not available:" << backend;
570 case Authenticator::IsReady:
571 // delete all other backends
572 _registeredAuthenticators.clear();
575 _authenticator = std::move(auth);
580 /*** Network Management ***/
582 bool Core::sslSupported()
585 SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
586 return sslServer && sslServer->isCertValid();
593 bool Core::reloadCerts()
596 SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
597 bool retv4 = sslServerv4->reloadCerts();
599 SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
600 bool retv6 = sslServerv6->reloadCerts();
602 return retv4 && retv6;
604 // SSL not supported, don't mark configuration reload as failed
610 void Core::cacheSysIdent()
612 if (isConfigured()) {
613 _authUserNames = _storage->getAllAuthUserNames();
618 QString Core::strictSysIdent(UserId user) const
620 if (_authUserNames.contains(user)) {
621 return _authUserNames[user];
624 // A new user got added since we last pulled our cache from the database.
625 // There's no way to avoid a database hit - we don't even know the authname!
626 instance()->cacheSysIdent();
628 if (_authUserNames.contains(user)) {
629 return _authUserNames[user];
632 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
633 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
634 return "unknown"; // Should we just terminate the program instead?
638 bool Core::startListening()
640 // in mono mode we only start a local port if a port is specified in the cli call
641 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
644 bool success = false;
645 uint port = Quassel::optionValue("port").toUInt();
647 const QString listen = Quassel::optionValue("listen");
648 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
649 if (listen_list.size() > 0) {
650 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
652 if (!addr.setAddress(listen_term)) {
653 qCritical() << qPrintable(
654 tr("Invalid listen address %1")
659 switch (addr.protocol()) {
660 case QAbstractSocket::IPv6Protocol:
661 if (_v6server.listen(addr, port)) {
662 quInfo() << qPrintable(
663 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
664 .arg(addr.toString())
665 .arg(_v6server.serverPort())
666 .arg(Quassel::buildInfo().protocolVersion)
671 quWarning() << qPrintable(
672 tr("Could not open IPv6 interface %1:%2: %3")
673 .arg(addr.toString())
675 .arg(_v6server.errorString()));
677 case QAbstractSocket::IPv4Protocol:
678 if (_server.listen(addr, port)) {
679 quInfo() << qPrintable(
680 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
681 .arg(addr.toString())
682 .arg(_server.serverPort())
683 .arg(Quassel::buildInfo().protocolVersion)
688 // if v6 succeeded on Any, the port will be already in use - don't display the error then
689 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
690 quWarning() << qPrintable(
691 tr("Could not open IPv4 interface %1:%2: %3")
692 .arg(addr.toString())
694 .arg(_server.errorString()));
698 qCritical() << qPrintable(
699 tr("Invalid listen address %1, unknown network protocol")
708 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
711 _identServer->startListening();
718 void Core::stopListening(const QString &reason)
721 _identServer->stopListening(reason);
724 bool wasListening = false;
725 if (_server.isListening()) {
729 if (_v6server.isListening()) {
734 if (reason.isEmpty())
735 quInfo() << "No longer listening for GUI clients.";
737 quInfo() << qPrintable(reason);
742 void Core::incomingConnection()
744 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
746 while (server->hasPendingConnections()) {
747 QTcpSocket *socket = server->nextPendingConnection();
749 CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
750 _connectingClients.insert(handler);
752 connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
753 connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
754 connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
756 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
759 stopListening(tr("Closing server for basic setup."));
765 // Potentially called during the initialization phase (before handing the connection off to the session)
766 void Core::clientDisconnected()
768 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
771 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
772 _connectingClients.remove(handler);
773 handler->deleteLater();
775 // make server listen again if still not configured
780 // TODO remove unneeded sessions - if necessary/possible...
781 // Suggestion: kill sessions if they are not connected to any network and client.
785 void Core::setupClientSession(RemotePeer *peer, UserId uid)
787 CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
790 // From now on everything is handled by the client session
791 disconnect(handler, 0, this, 0);
792 _connectingClients.remove(handler);
793 handler->deleteLater();
795 // Find or create session for validated user
798 // as we are currently handling an event triggered by incoming data on this socket
799 // it is unsafe to directly move the socket to the client thread.
800 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
804 void Core::customEvent(QEvent *event)
806 if (event->type() == AddClientEventId) {
807 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
808 addClientHelper(addClientEvent->peer, addClientEvent->userId);
814 void Core::addClientHelper(RemotePeer *peer, UserId uid)
816 // Find or create session for validated user
817 SessionThread *session = sessionForUser(uid);
818 session->addClient(peer);
822 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
824 if (_initialized && peer) {
825 setupInternalClientSession(peer);
828 _pendingInternalConnection = peer;
833 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
837 auto errorString = setupCoreForInternalUsage();
838 if (!errorString.isEmpty()) {
839 emit exitRequested(EXIT_FAILURE, errorString);
846 uid = _storage->internalUser();
849 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
850 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
855 quWarning() << "Client peer went away, not starting a session";
859 InternalPeer *corePeer = new InternalPeer(this);
860 corePeer->setPeer(clientPeer);
861 clientPeer->setPeer(corePeer);
863 // Find or create session for validated user
864 SessionThread *sessionThread = sessionForUser(uid);
865 sessionThread->addClient(corePeer);
869 SessionThread *Core::sessionForUser(UserId uid, bool restore)
871 if (_sessions.contains(uid))
872 return _sessions[uid];
874 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
878 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
880 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
884 QVariantList Core::backendInfo()
886 instance()->registerStorageBackends();
888 QVariantList backendInfos;
889 for (auto &&backend : instance()->_registeredStorageBackends) {
891 v["BackendId"] = backend->backendId();
892 v["DisplayName"] = backend->displayName();
893 v["Description"] = backend->description();
894 v["SetupData"] = backend->setupData(); // ignored by legacy clients
896 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
897 const auto &setupData = backend->setupData();
898 QStringList setupKeys;
899 QVariantMap setupDefaults;
900 for (int i = 0; i + 2 < setupData.size(); i += 3) {
901 setupKeys << setupData[i].toString();
902 setupDefaults[setupData[i].toString()] = setupData[i + 2];
904 v["SetupKeys"] = setupKeys;
905 v["SetupDefaults"] = setupDefaults;
906 // TODO Protocol Break: Remove
907 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
915 QVariantList Core::authenticatorInfo()
917 instance()->registerAuthenticators();
919 QVariantList authInfos;
920 for(auto &&backend : instance()->_registeredAuthenticators) {
922 v["BackendId"] = backend->backendId();
923 v["DisplayName"] = backend->displayName();
924 v["Description"] = backend->description();
925 v["SetupData"] = backend->setupData();
931 // migration / backend selection
932 bool Core::selectBackend(const QString &backend)
934 // reregister all storage backends
935 registerStorageBackends();
936 auto storage = storageBackend(backend);
938 QStringList backends;
939 std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
940 std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
941 return backend->displayName();
943 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
944 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
948 QVariantMap settings = promptForSettings(storage.get());
950 Storage::State storageState = storage->init(settings);
951 switch (storageState) {
952 case Storage::IsReady:
953 if (!saveBackendSettings(backend, settings)) {
954 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
956 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
957 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
959 case Storage::NotAvailable:
960 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
962 case Storage::NeedsSetup:
963 if (!storage->setup(settings)) {
964 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
968 if (storage->init(settings) != Storage::IsReady) {
969 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
973 if (!saveBackendSettings(backend, settings)) {
974 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
976 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
980 // let's see if we have a current storage object we can migrate from
981 auto reader = getMigrationReader(_storage.get());
982 auto writer = getMigrationWriter(storage.get());
983 if (reader && writer) {
984 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
987 if (reader->migrateTo(writer.get())) {
988 qDebug() << "Migration finished!";
989 qDebug() << qPrintable(tr("Migration finished!"));
990 if (!saveBackendSettings(backend, settings)) {
991 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
996 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
1000 // inform the user why we cannot merge
1002 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
1005 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
1008 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
1011 // so we were unable to merge, but let's create a user \o/
1012 _storage = std::move(storage);
1017 // TODO: I am not sure if this function is implemented correctly.
1018 // There is currently no concept of migraiton between auth backends.
1019 bool Core::selectAuthenticator(const QString &backend)
1021 // Register all authentication backends.
1022 registerAuthenticators();
1023 auto auther = authenticator(backend);
1025 QStringList authenticators;
1026 std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
1027 std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
1028 return authenticator->displayName();
1030 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
1031 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1035 QVariantMap settings = promptForSettings(auther.get());
1037 Authenticator::State state = auther->init(settings);
1039 case Authenticator::IsReady:
1040 saveAuthenticatorSettings(backend, settings);
1041 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1043 case Authenticator::NotAvailable:
1044 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1046 case Authenticator::NeedsSetup:
1047 if (!auther->setup(settings)) {
1048 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1052 if (auther->init(settings) != Authenticator::IsReady) {
1053 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1057 saveAuthenticatorSettings(backend, settings);
1058 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1061 _authenticator = std::move(auther);
1066 bool Core::createUser()
1068 QTextStream out(stdout);
1069 QTextStream in(stdin);
1070 out << "Add a new user:" << endl;
1071 out << "Username: ";
1073 QString username = in.readLine().trimmed();
1076 out << "Password: ";
1078 QString password = in.readLine().trimmed();
1080 out << "Repeat Password: ";
1082 QString password2 = in.readLine().trimmed();
1086 if (password != password2) {
1087 quWarning() << "Passwords don't match!";
1090 if (password.isEmpty()) {
1091 quWarning() << "Password is empty!";
1095 if (_configured && _storage->addUser(username, password).isValid()) {
1096 out << "Added user " << username << " successfully!" << endl;
1100 quWarning() << "Unable to add user:" << qPrintable(username);
1106 bool Core::changeUserPass(const QString &username)
1108 QTextStream out(stdout);
1109 QTextStream in(stdin);
1110 UserId userId = _storage->getUserId(username);
1111 if (!userId.isValid()) {
1112 out << "User " << username << " does not exist." << endl;
1116 if (!canChangeUserPassword(userId)) {
1117 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1121 out << "Change password for user: " << username << endl;
1124 out << "New Password: ";
1126 QString password = in.readLine().trimmed();
1128 out << "Repeat Password: ";
1130 QString password2 = in.readLine().trimmed();
1134 if (password != password2) {
1135 quWarning() << "Passwords don't match!";
1138 if (password.isEmpty()) {
1139 quWarning() << "Password is empty!";
1143 if (_configured && _storage->updateUser(userId, password)) {
1144 out << "Password changed successfully!" << endl;
1148 quWarning() << "Failed to change password!";
1154 bool Core::changeUserPassword(UserId userId, const QString &password)
1156 if (!isConfigured() || !userId.isValid())
1159 if (!canChangeUserPassword(userId))
1162 return instance()->_storage->updateUser(userId, password);
1165 // TODO: this code isn't currently 100% optimal because the core
1166 // doesn't know it can have multiple auth providers configured (there aren't
1167 // multiple auth providers at the moment anyway) and we have hardcoded the
1168 // Database provider to be always allowed.
1169 bool Core::canChangeUserPassword(UserId userId)
1171 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1172 if (authProvider != "Database") {
1173 if (authProvider != instance()->_authenticator->backendId()) {
1176 else if (instance()->_authenticator->canChangePassword()) {
1184 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
1189 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1191 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1195 return sqlStorage->createMigrationReader();
1199 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
1204 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1206 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1210 return sqlStorage->createMigrationWriter();
1214 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1216 QVariantMap dbsettings;
1217 dbsettings["Backend"] = backend;
1218 dbsettings["ConnectionProperties"] = settings;
1219 CoreSettings s = CoreSettings();
1220 s.setStorageSettings(dbsettings);
1225 void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings)
1227 QVariantMap dbsettings;
1228 dbsettings["Authenticator"] = backend;
1229 dbsettings["AuthProperties"] = settings;
1230 CoreSettings().setAuthSettings(dbsettings);
1233 // Generic version of promptForSettings that doesn't care what *type* of
1234 // backend it runs over.
1235 template<typename Backend>
1236 QVariantMap Core::promptForSettings(const Backend *backend)
1238 QVariantMap settings;
1239 const QVariantList& setupData = backend->setupData();
1241 if (setupData.isEmpty())
1244 QTextStream out(stdout);
1245 QTextStream in(stdin);
1246 out << "Default values are in brackets" << endl;
1248 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1249 QString key = setupData[i].toString();
1250 out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
1252 bool noEcho = key.toLower().contains("password");
1256 QString input = in.readLine().trimmed();
1262 QVariant value{setupData[i+2]};
1263 if (!input.isEmpty()) {
1264 switch (value.type()) {
1266 value = input.toInt();
1272 settings[key] = value;
1279 void Core::stdInEcho(bool on)
1281 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1283 GetConsoleMode(hStdin, &mode);
1285 mode |= ENABLE_ECHO_INPUT;
1287 mode &= ~ENABLE_ECHO_INPUT;
1288 SetConsoleMode(hStdin, mode);
1292 void Core::stdInEcho(bool on)
1295 tcgetattr(STDIN_FILENO, &t);
1300 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1303 #endif /* Q_OS_WIN */