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 ***************************************************************************/
25 #include <QCoreApplication>
27 #include "coreauthhandler.h"
28 #include "coresession.h"
29 #include "coresettings.h"
30 #include "internalpeer.h"
31 #include "logmessage.h"
33 #include "postgresqlstorage.h"
35 #include "sqlauthenticator.h"
36 #include "sqlitestorage.h"
41 # include "ldapauthenticator.h"
53 // ==============================
55 // ==============================
56 const int Core::AddClientEventId = QEvent::registerEventType();
58 class AddClientEvent : public QEvent
61 AddClientEvent(RemotePeer* p, UserId uid)
62 : QEvent(QEvent::Type(Core::AddClientEventId))
70 // ==============================
72 // ==============================
75 : Singleton<Core>{this}
79 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
80 // thread, they get moved with it
81 _server.setParent(this);
82 _v6server.setParent(this);
83 _storageSyncTimer.setParent(this);
88 qDeleteAll(_connectingClients);
89 qDeleteAll(_sessions);
95 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
97 // check settings version
98 // so far, we only have 1
100 if (s.version() != 1) {
101 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
104 // Set up storage and authentication backends
105 registerStorageBackends();
106 registerAuthenticators();
108 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
109 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
112 QVariantMap db_connectionProperties;
114 QString auth_authenticator;
115 QVariantMap auth_properties;
117 bool writeError = false;
119 if (config_from_environment) {
120 db_backend = environment.value("DB_BACKEND");
121 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
126 QVariantMap dbsettings = cs.storageSettings().toMap();
127 db_backend = dbsettings.value("Backend").toString();
128 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
130 QVariantMap authSettings = cs.authSettings().toMap();
131 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
132 auth_properties = authSettings.value("AuthProperties").toMap();
134 writeError = !cs.isWritable();
138 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
140 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
143 catch (ExitException) {
148 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
150 if (Quassel::isOptionSet("select-backend")) {
151 success &= selectBackend(Quassel::optionValue("select-backend"));
153 if (Quassel::isOptionSet("select-authenticator")) {
154 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
156 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
160 if (config_from_environment) {
162 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
164 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
167 catch (ExitException e) {
168 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
172 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
176 if (_registeredStorageBackends.empty()) {
177 throw ExitException{EXIT_FAILURE,
178 tr("Could not initialize any storage backend! Exiting...\n"
179 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
180 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
185 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
188 quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
192 if (Quassel::isOptionSet("add-user")) {
193 bool success = createUser();
194 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
197 if (Quassel::isOptionSet("change-userpass")) {
198 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
199 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
202 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
203 if (_strictIdentEnabled) {
207 if (Quassel::isOptionSet("oidentd")) {
208 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
211 if (Quassel::isOptionSet("ident-daemon")) {
212 _identServer = new IdentServer(this);
215 Quassel::registerReloadHandler([]() {
216 // Currently, only reloading SSL certificates and the sysident cache is supported
217 if (Core::instance()) {
218 Core::instance()->cacheSysIdent();
219 Core::instance()->reloadCerts();
225 connect(&_storageSyncTimer, &QTimer::timeout, this, &Core::syncStorage);
226 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
229 connect(&_server, &QTcpServer::newConnection, this, &Core::incomingConnection);
230 connect(&_v6server, &QTcpServer::newConnection, this, &Core::incomingConnection);
232 if (!startListening()) {
233 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
236 if (_configured && !Quassel::isOptionSet("norestore")) {
237 Core::restoreState();
242 if (_pendingInternalConnection) {
243 connectInternalPeer(_pendingInternalConnection);
244 _pendingInternalConnection = {};
248 void Core::initAsync()
253 catch (ExitException e) {
254 emit exitRequested(e.exitCode, e.errorString);
258 void Core::shutdown()
260 quInfo() << "Core shutting down...";
264 for (auto&& client : _connectingClients) {
265 client->deleteLater();
267 _connectingClients.clear();
269 if (_sessions.isEmpty()) {
270 emit shutdownComplete();
274 for (auto&& session : _sessions) {
275 connect(session, &SessionThread::shutdownComplete, this, &Core::onSessionShutdown);
280 void Core::onSessionShutdown(SessionThread* session)
282 _sessions.take(_sessions.key(session))->deleteLater();
283 if (_sessions.isEmpty()) {
284 quInfo() << "Core shutdown complete!";
285 emit shutdownComplete();
289 /*** Session Restore ***/
291 void Core::saveState()
294 QVariantList activeSessions;
295 for (auto&& user : instance()->_sessions.keys())
296 activeSessions << QVariant::fromValue<UserId>(user);
297 _storage->setCoreState(activeSessions);
301 void Core::restoreState()
304 quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
307 if (_sessions.count()) {
308 quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
313 /* We don't check, since we are at the first version since switching to Git
314 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
316 quWarning() << qPrintable(tr("Core state too old, ignoring..."));
321 const QList<QVariant>& activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
322 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
324 if (activeSessions.count() > 0) {
325 quInfo() << "Restoring previous core state...";
326 for (auto&& v : activeSessions) {
327 UserId user = v.value<UserId>();
328 sessionForUser(user, true);
335 QString Core::setup(const QString& adminUser,
336 const QString& adminPassword,
337 const QString& backend,
338 const QVariantMap& setupData,
339 const QString& authenticator,
340 const QVariantMap& authSetupData)
342 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
345 QString Core::setupCore(const QString& adminUser,
346 const QString& adminPassword,
347 const QString& backend,
348 const QVariantMap& setupData,
349 const QString& authenticator,
350 const QVariantMap& authSetupData)
353 return tr("Core is already configured! Not configuring again...");
355 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
356 return tr("Admin user or password not set.");
359 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
360 return tr("Could not setup storage!");
363 quInfo() << "Selected authenticator:" << authenticator;
364 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) {
365 return tr("Could not setup authenticator!");
368 catch (ExitException e) {
369 // Event loop is running, so trigger an exit rather than throwing an exception
370 QCoreApplication::exit(e.exitCode);
371 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
374 if (!saveBackendSettings(backend, setupData)) {
375 return tr("Could not save backend settings, probably a permission problem.");
377 saveAuthenticatorSettings(authenticator, authSetupData);
379 quInfo() << qPrintable(tr("Creating admin user..."));
380 _storage->addUser(adminUser, adminPassword);
382 startListening(); // TODO check when we need this
386 QString Core::setupCoreForInternalUsage()
388 Q_ASSERT(!_registeredStorageBackends.empty());
390 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
392 for (int i = 0; i < 10; i++) {
394 pass += qrand() % 10;
397 // mono client currently needs sqlite
398 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
401 /*** Storage Handling ***/
403 template<typename Storage>
404 void Core::registerStorageBackend()
406 auto backend = makeDeferredShared<Storage>(this);
407 if (backend->isAvailable())
408 _registeredStorageBackends.emplace_back(std::move(backend));
410 backend->deleteLater();
413 void Core::registerStorageBackends()
415 if (_registeredStorageBackends.empty()) {
416 registerStorageBackend<SqliteStorage>();
417 registerStorageBackend<PostgreSqlStorage>();
421 DeferredSharedPtr<Storage> Core::storageBackend(const QString& backendId) const
423 auto it = std::find_if(_registeredStorageBackends.begin(),
424 _registeredStorageBackends.end(),
425 [backendId](const DeferredSharedPtr<Storage>& backend) { return backend->displayName() == backendId; });
426 return it != _registeredStorageBackends.end() ? *it : nullptr;
429 bool Core::initStorage(
430 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
432 if (backend.isEmpty()) {
433 quWarning() << "No storage backend selected!";
437 auto storage = storageBackend(backend);
439 qCritical() << "Selected storage backend is not available:" << backend;
443 connect(storage.get(), &Storage::dbUpgradeInProgress, this, &Core::dbUpgradeInProgress);
445 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
446 switch (storageState) {
447 case Storage::NeedsSetup:
449 return false; // trigger setup process
450 if (storage->setup(settings, environment, loadFromEnvironment))
451 return initStorage(backend, settings, environment, loadFromEnvironment, false);
454 case Storage::NotAvailable:
456 // If initialization wasn't successful, we quit to keep from coming up unconfigured
457 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
459 qCritical() << "Selected storage backend is not available:" << backend;
462 case Storage::IsReady:
463 // delete all other backends
464 _registeredStorageBackends.clear();
465 connect(storage.get(), &Storage::bufferInfoUpdated, this, &Core::bufferInfoUpdated);
468 _storage = std::move(storage);
472 void Core::syncStorage()
478 /*** Storage Access ***/
479 bool Core::createNetwork(UserId user, NetworkInfo& info)
481 NetworkId networkId = instance()->_storage->createNetwork(user, info);
482 if (!networkId.isValid())
485 info.networkId = networkId;
489 /*** Authenticators ***/
491 // Authentication handling, now independent from storage.
492 template<typename Authenticator>
493 void Core::registerAuthenticator()
495 auto authenticator = makeDeferredShared<Authenticator>(this);
496 if (authenticator->isAvailable())
497 _registeredAuthenticators.emplace_back(std::move(authenticator));
499 authenticator->deleteLater();
502 void Core::registerAuthenticators()
504 if (_registeredAuthenticators.empty()) {
505 registerAuthenticator<SqlAuthenticator>();
507 registerAuthenticator<LdapAuthenticator>();
512 DeferredSharedPtr<Authenticator> Core::authenticator(const QString& backendId) const
514 auto it = std::find_if(_registeredAuthenticators.begin(),
515 _registeredAuthenticators.end(),
516 [backendId](const DeferredSharedPtr<Authenticator>& authenticator) {
517 return authenticator->backendId() == backendId;
519 return it != _registeredAuthenticators.end() ? *it : nullptr;
522 // FIXME: Apparently, this is the legacy way of initting storage backends?
523 // If there's a not-legacy way, it should be used here
524 bool Core::initAuthenticator(
525 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
527 if (backend.isEmpty()) {
528 quWarning() << "No authenticator selected!";
532 auto auth = authenticator(backend);
534 qCritical() << "Selected auth backend is not available:" << backend;
538 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
540 case Authenticator::NeedsSetup:
542 return false; // trigger setup process
543 if (auth->setup(settings, environment, loadFromEnvironment))
544 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
547 case Authenticator::NotAvailable:
549 // If initialization wasn't successful, we quit to keep from coming up unconfigured
550 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
552 qCritical() << "Selected auth backend is not available:" << backend;
555 case Authenticator::IsReady:
556 // delete all other backends
557 _registeredAuthenticators.clear();
560 _authenticator = std::move(auth);
564 /*** Network Management ***/
566 bool Core::sslSupported()
569 auto* sslServer = qobject_cast<SslServer*>(&instance()->_server);
570 return sslServer && sslServer->isCertValid();
576 bool Core::reloadCerts()
579 auto* sslServerv4 = qobject_cast<SslServer*>(&_server);
580 bool retv4 = sslServerv4->reloadCerts();
582 auto* sslServerv6 = qobject_cast<SslServer*>(&_v6server);
583 bool retv6 = sslServerv6->reloadCerts();
585 return retv4 && retv6;
587 // SSL not supported, don't mark configuration reload as failed
592 void Core::cacheSysIdent()
594 if (isConfigured()) {
595 _authUserNames = _storage->getAllAuthUserNames();
599 QString Core::strictSysIdent(UserId user) const
601 if (_authUserNames.contains(user)) {
602 return _authUserNames[user];
605 // A new user got added since we last pulled our cache from the database.
606 // There's no way to avoid a database hit - we don't even know the authname!
607 instance()->cacheSysIdent();
609 if (_authUserNames.contains(user)) {
610 return _authUserNames[user];
613 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
614 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
615 return "unknown"; // Should we just terminate the program instead?
618 bool Core::startListening()
620 // in mono mode we only start a local port if a port is specified in the cli call
621 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
624 bool success = false;
625 uint port = Quassel::optionValue("port").toUInt();
627 const QString listen = Quassel::optionValue("listen");
628 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
629 if (listen_list.size() > 0) {
630 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
632 if (!addr.setAddress(listen_term)) {
633 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
636 switch (addr.protocol()) {
637 case QAbstractSocket::IPv6Protocol:
638 if (_v6server.listen(addr, port)) {
639 quInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
640 .arg(addr.toString())
641 .arg(_v6server.serverPort())
642 .arg(Quassel::buildInfo().protocolVersion));
646 quWarning() << qPrintable(
647 tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
649 case QAbstractSocket::IPv4Protocol:
650 if (_server.listen(addr, port)) {
651 quInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
652 .arg(addr.toString())
653 .arg(_server.serverPort())
654 .arg(Quassel::buildInfo().protocolVersion));
658 // if v6 succeeded on Any, the port will be already in use - don't display the error then
659 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
660 quWarning() << qPrintable(
661 tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
665 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
672 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
675 _identServer->startListening();
681 void Core::stopListening(const QString& reason)
684 _identServer->stopListening(reason);
687 bool wasListening = false;
688 if (_server.isListening()) {
692 if (_v6server.isListening()) {
697 if (reason.isEmpty())
698 quInfo() << "No longer listening for GUI clients.";
700 quInfo() << qPrintable(reason);
704 void Core::incomingConnection()
706 auto* server = qobject_cast<QTcpServer*>(sender());
708 while (server->hasPendingConnections()) {
709 QTcpSocket* socket = server->nextPendingConnection();
711 auto* handler = new CoreAuthHandler(socket, this);
712 _connectingClients.insert(handler);
714 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
715 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
716 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
718 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
721 stopListening(tr("Closing server for basic setup."));
726 // Potentially called during the initialization phase (before handing the connection off to the session)
727 void Core::clientDisconnected()
729 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
732 quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
733 _connectingClients.remove(handler);
734 handler->deleteLater();
736 // make server listen again if still not configured
741 // TODO remove unneeded sessions - if necessary/possible...
742 // Suggestion: kill sessions if they are not connected to any network and client.
745 void Core::setupClientSession(RemotePeer* peer, UserId uid)
747 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
750 // From now on everything is handled by the client session
751 disconnect(handler, nullptr, this, nullptr);
752 _connectingClients.remove(handler);
753 handler->deleteLater();
755 // Find or create session for validated user
758 // as we are currently handling an event triggered by incoming data on this socket
759 // it is unsafe to directly move the socket to the client thread.
760 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
763 void Core::customEvent(QEvent* event)
765 if (event->type() == AddClientEventId) {
766 auto* addClientEvent = static_cast<AddClientEvent*>(event);
767 addClientHelper(addClientEvent->peer, addClientEvent->userId);
772 void Core::addClientHelper(RemotePeer* peer, UserId uid)
774 // Find or create session for validated user
775 SessionThread* session = sessionForUser(uid);
776 session->addClient(peer);
779 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
781 if (_initialized && peer) {
782 setupInternalClientSession(peer);
785 _pendingInternalConnection = peer;
789 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
793 auto errorString = setupCoreForInternalUsage();
794 if (!errorString.isEmpty()) {
795 emit exitRequested(EXIT_FAILURE, errorString);
802 uid = _storage->internalUser();
805 quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
806 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
811 quWarning() << "Client peer went away, not starting a session";
815 auto* corePeer = new InternalPeer(this);
816 corePeer->setPeer(clientPeer);
817 clientPeer->setPeer(corePeer);
819 // Find or create session for validated user
820 SessionThread* sessionThread = sessionForUser(uid);
821 sessionThread->addClient(corePeer);
824 SessionThread* Core::sessionForUser(UserId uid, bool restore)
826 if (_sessions.contains(uid))
827 return _sessions[uid];
829 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
832 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
834 quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
837 QVariantList Core::backendInfo()
839 instance()->registerStorageBackends();
841 QVariantList backendInfos;
842 for (auto&& backend : instance()->_registeredStorageBackends) {
844 v["BackendId"] = backend->backendId();
845 v["DisplayName"] = backend->displayName();
846 v["Description"] = backend->description();
847 v["SetupData"] = backend->setupData(); // ignored by legacy clients
849 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
850 const auto& setupData = backend->setupData();
851 QStringList setupKeys;
852 QVariantMap setupDefaults;
853 for (int i = 0; i + 2 < setupData.size(); i += 3) {
854 setupKeys << setupData[i].toString();
855 setupDefaults[setupData[i].toString()] = setupData[i + 2];
857 v["SetupKeys"] = setupKeys;
858 v["SetupDefaults"] = setupDefaults;
859 // TODO Protocol Break: Remove
860 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
867 QVariantList Core::authenticatorInfo()
869 instance()->registerAuthenticators();
871 QVariantList authInfos;
872 for (auto&& backend : instance()->_registeredAuthenticators) {
874 v["BackendId"] = backend->backendId();
875 v["DisplayName"] = backend->displayName();
876 v["Description"] = backend->description();
877 v["SetupData"] = backend->setupData();
883 // migration / backend selection
884 bool Core::selectBackend(const QString& backend)
886 // reregister all storage backends
887 registerStorageBackends();
888 auto storage = storageBackend(backend);
890 QStringList backends;
891 std::transform(_registeredStorageBackends.begin(),
892 _registeredStorageBackends.end(),
893 std::back_inserter(backends),
894 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
895 quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
896 quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
900 QVariantMap settings = promptForSettings(storage.get());
902 Storage::State storageState = storage->init(settings);
903 switch (storageState) {
904 case Storage::IsReady:
905 if (!saveBackendSettings(backend, settings)) {
906 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
908 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
909 quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
911 case Storage::NotAvailable:
912 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
914 case Storage::NeedsSetup:
915 if (!storage->setup(settings)) {
916 quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
920 if (storage->init(settings) != Storage::IsReady) {
921 quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
925 if (!saveBackendSettings(backend, settings)) {
926 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
928 quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
932 // let's see if we have a current storage object we can migrate from
933 auto reader = getMigrationReader(_storage.get());
934 auto writer = getMigrationWriter(storage.get());
935 if (reader && writer) {
936 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
939 if (reader->migrateTo(writer.get())) {
940 qDebug() << "Migration finished!";
941 qDebug() << qPrintable(tr("Migration finished!"));
942 if (!saveBackendSettings(backend, settings)) {
943 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
948 quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
952 // inform the user why we cannot merge
954 quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
957 quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
960 quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
963 // so we were unable to merge, but let's create a user \o/
964 _storage = std::move(storage);
969 // TODO: I am not sure if this function is implemented correctly.
970 // There is currently no concept of migraiton between auth backends.
971 bool Core::selectAuthenticator(const QString& backend)
973 // Register all authentication backends.
974 registerAuthenticators();
975 auto auther = authenticator(backend);
977 QStringList authenticators;
978 std::transform(_registeredAuthenticators.begin(),
979 _registeredAuthenticators.end(),
980 std::back_inserter(authenticators),
981 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
982 quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
983 quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
987 QVariantMap settings = promptForSettings(auther.get());
989 Authenticator::State state = auther->init(settings);
991 case Authenticator::IsReady:
992 saveAuthenticatorSettings(backend, settings);
993 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
995 case Authenticator::NotAvailable:
996 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
998 case Authenticator::NeedsSetup:
999 if (!auther->setup(settings)) {
1000 quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1004 if (auther->init(settings) != Authenticator::IsReady) {
1005 quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1009 saveAuthenticatorSettings(backend, settings);
1010 quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1013 _authenticator = std::move(auther);
1017 bool Core::createUser()
1019 QTextStream out(stdout);
1020 QTextStream in(stdin);
1021 out << "Add a new user:" << endl;
1022 out << "Username: ";
1024 QString username = in.readLine().trimmed();
1027 out << "Password: ";
1029 QString password = in.readLine().trimmed();
1031 out << "Repeat Password: ";
1033 QString password2 = in.readLine().trimmed();
1037 if (password != password2) {
1038 quWarning() << "Passwords don't match!";
1041 if (password.isEmpty()) {
1042 quWarning() << "Password is empty!";
1046 if (_configured && _storage->addUser(username, password).isValid()) {
1047 out << "Added user " << username << " successfully!" << endl;
1051 quWarning() << "Unable to add user:" << qPrintable(username);
1056 bool Core::changeUserPass(const QString& username)
1058 QTextStream out(stdout);
1059 QTextStream in(stdin);
1060 UserId userId = _storage->getUserId(username);
1061 if (!userId.isValid()) {
1062 out << "User " << username << " does not exist." << endl;
1066 if (!canChangeUserPassword(userId)) {
1067 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1071 out << "Change password for user: " << username << endl;
1074 out << "New Password: ";
1076 QString password = in.readLine().trimmed();
1078 out << "Repeat Password: ";
1080 QString password2 = in.readLine().trimmed();
1084 if (password != password2) {
1085 quWarning() << "Passwords don't match!";
1088 if (password.isEmpty()) {
1089 quWarning() << "Password is empty!";
1093 if (_configured && _storage->updateUser(userId, password)) {
1094 out << "Password changed successfully!" << endl;
1098 quWarning() << "Failed to change password!";
1103 bool Core::changeUserPassword(UserId userId, const QString& password)
1105 if (!isConfigured() || !userId.isValid())
1108 if (!canChangeUserPassword(userId))
1111 return instance()->_storage->updateUser(userId, password);
1114 // TODO: this code isn't currently 100% optimal because the core
1115 // doesn't know it can have multiple auth providers configured (there aren't
1116 // multiple auth providers at the moment anyway) and we have hardcoded the
1117 // Database provider to be always allowed.
1118 bool Core::canChangeUserPassword(UserId userId)
1120 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1121 if (authProvider != "Database") {
1122 if (authProvider != instance()->_authenticator->backendId()) {
1125 else if (instance()->_authenticator->canChangePassword()) {
1132 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1137 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1139 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1143 return sqlStorage->createMigrationReader();
1146 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1151 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1153 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1157 return sqlStorage->createMigrationWriter();
1160 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1162 QVariantMap dbsettings;
1163 dbsettings["Backend"] = backend;
1164 dbsettings["ConnectionProperties"] = settings;
1165 CoreSettings s = CoreSettings();
1166 s.setStorageSettings(dbsettings);
1170 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1172 QVariantMap dbsettings;
1173 dbsettings["Authenticator"] = backend;
1174 dbsettings["AuthProperties"] = settings;
1175 CoreSettings().setAuthSettings(dbsettings);
1178 // Generic version of promptForSettings that doesn't care what *type* of
1179 // backend it runs over.
1180 template<typename Backend>
1181 QVariantMap Core::promptForSettings(const Backend* backend)
1183 QVariantMap settings;
1184 const QVariantList& setupData = backend->setupData();
1186 if (setupData.isEmpty())
1189 QTextStream out(stdout);
1190 QTextStream in(stdin);
1191 out << "Default values are in brackets" << endl;
1193 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1194 QString key = setupData[i].toString();
1195 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1197 bool noEcho = key.toLower().contains("password");
1201 QString input = in.readLine().trimmed();
1207 QVariant value{setupData[i + 2]};
1208 if (!input.isEmpty()) {
1209 switch (value.type()) {
1211 value = input.toInt();
1217 settings[key] = value;
1223 void Core::stdInEcho(bool on)
1225 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1227 GetConsoleMode(hStdin, &mode);
1229 mode |= ENABLE_ECHO_INPUT;
1231 mode &= ~ENABLE_ECHO_INPUT;
1232 SetConsoleMode(hStdin, mode);
1236 void Core::stdInEcho(bool on)
1239 tcgetattr(STDIN_FILENO, &t);
1244 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1247 #endif /* Q_OS_WIN */