+#include "types.h"
+#include "util.h"
+
+// Currently building with LDAP bindings is optional.
+#ifdef HAVE_LDAP
+#include "ldapauthenticator.h"
+#endif
+
+// migration related
+#include <QFile>
+#ifdef Q_OS_WIN
+# include <windows.h>
+#else
+# include <unistd.h>
+# include <termios.h>
+#endif /* Q_OS_WIN */
+
+// ==============================
+// Custom Events
+// ==============================
+const int Core::AddClientEventId = QEvent::registerEventType();
+
+class AddClientEvent : public QEvent
+{
+public:
+ AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
+ RemotePeer *peer;
+ UserId userId;
+};
+
+
+// ==============================
+// Core
+// ==============================
+
+Core::Core()
+ : Singleton<Core>{this}
+{
+ // Parent all QObject-derived attributes, so when the Core instance gets moved into another
+ // thread, they get moved with it
+ _server.setParent(this);
+ _v6server.setParent(this);
+ _storageSyncTimer.setParent(this);
+}
+
+
+Core::~Core()
+{
+ qDeleteAll(_connectingClients);
+ qDeleteAll(_sessions);
+ syncStorage();
+}
+
+
+void Core::init()
+{
+ _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
+
+ if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
+ Quassel::loadTranslation(QLocale::system());
+ }
+
+ // check settings version
+ // so far, we only have 1
+ CoreSettings s;
+ if (s.version() != 1) {
+ throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
+ }
+
+ // Set up storage and authentication backends
+ registerStorageBackends();
+ registerAuthenticators();
+
+ QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
+ bool config_from_environment = Quassel::isOptionSet("config-from-environment");
+
+ QString db_backend;
+ QVariantMap db_connectionProperties;
+
+ QString auth_authenticator;
+ QVariantMap auth_properties;
+
+ bool writeError = false;
+
+ if (config_from_environment) {
+ db_backend = environment.value("DB_BACKEND");
+ auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
+ }
+ else {
+ CoreSettings cs;
+
+ QVariantMap dbsettings = cs.storageSettings().toMap();
+ db_backend = dbsettings.value("Backend").toString();
+ db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
+
+ QVariantMap authSettings = cs.authSettings().toMap();
+ auth_authenticator = authSettings.value("Authenticator", "Database").toString();
+ auth_properties = authSettings.value("AuthProperties").toMap();
+
+ writeError = !cs.isWritable();
+ }
+
+ try {
+ _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
+ if (_configured) {
+ _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
+ }
+ }
+ catch (ExitException) {
+ // Try again later
+ _configured = false;
+ }
+
+ if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
+ bool success{true};
+ if (Quassel::isOptionSet("select-backend")) {
+ success &= selectBackend(Quassel::optionValue("select-backend"));
+ }
+ if (Quassel::isOptionSet("select-authenticator")) {
+ success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
+ }
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
+ }
+
+ if (!_configured) {
+ if (config_from_environment) {
+ try {
+ _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
+ if (_configured) {
+ _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
+ }
+ }
+ catch (ExitException e) {
+ throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
+ }
+
+ if (!_configured) {
+ throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
+ }
+ }
+ else {
+ if (_registeredStorageBackends.empty()) {
+ throw ExitException{EXIT_FAILURE,
+ tr("Could not initialize any storage backend! Exiting...\n"
+ "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
+ "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
+ "to work.")};
+ }
+
+ if (writeError) {
+ throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
+ }
+
+ quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
+ }
+ }
+ else {
+ if (Quassel::isOptionSet("add-user")) {
+ bool success = createUser();
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
+ }
+
+ if (Quassel::isOptionSet("change-userpass")) {
+ bool success = changeUserPass(Quassel::optionValue("change-userpass"));
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
+ }
+
+ _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
+ if (_strictIdentEnabled) {
+ cacheSysIdent();
+ }
+
+ if (Quassel::isOptionSet("oidentd")) {
+ _oidentdConfigGenerator = new OidentdConfigGenerator(this);
+ }
+
+
+ if (Quassel::isOptionSet("ident-daemon")) {
+ _identServer = new IdentServer(this);
+ }
+
+ Quassel::registerReloadHandler([]() {
+ // Currently, only reloading SSL certificates and the sysident cache is supported
+ if (Core::instance()) {
+ Core::instance()->cacheSysIdent();
+ Core::instance()->reloadCerts();
+ return true;
+ }
+ return false;
+ });
+
+ connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
+ _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
+ }
+
+ connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
+ connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
+
+ if (!startListening()) {
+ throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
+ }
+
+ if (_configured && !Quassel::isOptionSet("norestore")) {
+ Core::restoreState();
+ }
+
+ _initialized = true;
+
+ if (_pendingInternalConnection) {
+ connectInternalPeer(_pendingInternalConnection);
+ _pendingInternalConnection = {};
+ }
+}
+
+
+void Core::initAsync()
+{
+ try {
+ init();
+ }
+ catch (ExitException e) {
+ emit exitRequested(e.exitCode, e.errorString);
+ }
+}
+
+
+void Core::shutdown()
+{
+ quInfo() << "Core shutting down...";
+
+ saveState();
+
+ for (auto &&client : _connectingClients) {
+ client->deleteLater();
+ }
+ _connectingClients.clear();
+
+ if (_sessions.isEmpty()) {
+ emit shutdownComplete();
+ return;
+ }
+
+ for (auto &&session : _sessions) {
+ connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
+ session->shutdown();
+ }
+}
+
+
+void Core::onSessionShutdown(SessionThread *session)
+{
+ _sessions.take(_sessions.key(session))->deleteLater();
+ if (_sessions.isEmpty()) {
+ quInfo() << "Core shutdown complete!";
+ emit shutdownComplete();
+ }
+}