Make authenticator changes to protocol backwards-compatible
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include <QCoreApplication>
22
23 #include "core.h"
24 #include "coreauthhandler.h"
25 #include "coresession.h"
26 #include "coresettings.h"
27 #include "logger.h"
28 #include "internalpeer.h"
29 #include "network.h"
30 #include "postgresqlstorage.h"
31 #include "quassel.h"
32 #include "sqlauthenticator.h"
33 #include "sqlitestorage.h"
34 #include "util.h"
35
36 // Currently building with LDAP bindings is optional.
37 #ifdef HAVE_LDAP
38 #include "ldapauthenticator.h"
39 #endif
40
41 // migration related
42 #include <QFile>
43 #ifdef Q_OS_WIN
44 #  include <windows.h>
45 #else
46 #  include <unistd.h>
47 #  include <termios.h>
48 #endif /* Q_OS_WIN */
49
50 #ifdef HAVE_UMASK
51 #  include <sys/types.h>
52 #  include <sys/stat.h>
53 #endif /* HAVE_UMASK */
54
55 // ==============================
56 //  Custom Events
57 // ==============================
58 const int Core::AddClientEventId = QEvent::registerEventType();
59
60 class AddClientEvent : public QEvent
61 {
62 public:
63     AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
64     RemotePeer *peer;
65     UserId userId;
66 };
67
68
69 // ==============================
70 //  Core
71 // ==============================
72 Core *Core::instanceptr = 0;
73
74 Core *Core::instance()
75 {
76     if (instanceptr) return instanceptr;
77     instanceptr = new Core();
78     instanceptr->init();
79     return instanceptr;
80 }
81
82
83 void Core::destroy()
84 {
85     delete instanceptr;
86     instanceptr = 0;
87 }
88
89
90 Core::Core()
91     : QObject(),
92       _storage(0),
93       _authenticator(0)
94 {
95 #ifdef HAVE_UMASK
96     umask(S_IRWXG | S_IRWXO);
97 #endif
98     _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
99
100     Quassel::loadTranslation(QLocale::system());
101
102     // FIXME: MIGRATION 0.3 -> 0.4: Move database and core config to new location
103     // Move settings, note this does not delete the old files
104 #ifdef Q_OS_MAC
105     QSettings newSettings("quassel-irc.org", "quasselcore");
106 #else
107
108 # ifdef Q_OS_WIN
109     QSettings::Format format = QSettings::IniFormat;
110 # else
111     QSettings::Format format = QSettings::NativeFormat;
112 # endif
113     QString newFilePath = Quassel::configDirPath() + "quasselcore"
114                           + ((format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
115     QSettings newSettings(newFilePath, format);
116 #endif /* Q_OS_MAC */
117
118     if (newSettings.value("Config/Version").toUInt() == 0) {
119 #   ifdef Q_OS_MAC
120         QString org = "quassel-irc.org";
121 #   else
122         QString org = "Quassel Project";
123 #   endif
124         QSettings oldSettings(org, "Quassel Core");
125         if (oldSettings.allKeys().count()) {
126             qWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
127             foreach(QString key, oldSettings.allKeys())
128             newSettings.setValue(key, oldSettings.value(key));
129             newSettings.setValue("Config/Version", 1);
130             qWarning() << "*   Your core settings have been migrated to" << newSettings.fileName();
131
132 #ifndef Q_OS_MAC /* we don't need to move the db and cert for mac */
133 #ifdef Q_OS_WIN
134             QString quasselDir = qgetenv("APPDATA") + "/quassel/";
135 #elif defined Q_OS_MAC
136             QString quasselDir = QDir::homePath() + "/Library/Application Support/Quassel/";
137 #else
138             QString quasselDir = QDir::homePath() + "/.quassel/";
139 #endif
140
141             QFileInfo info(Quassel::configDirPath() + "quassel-storage.sqlite");
142             if (!info.exists()) {
143                 // move database, if we found it
144                 QFile oldDb(quasselDir + "quassel-storage.sqlite");
145                 if (oldDb.exists()) {
146                     bool success = oldDb.rename(Quassel::configDirPath() + "quassel-storage.sqlite");
147                     if (success)
148                         qWarning() << "*   Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
149                     else
150                         qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
151                 }
152             }
153             // move certificate
154             QFileInfo certInfo(quasselDir + "quasselCert.pem");
155             if (certInfo.exists()) {
156                 QFile cert(quasselDir + "quasselCert.pem");
157                 bool success = cert.rename(Quassel::configDirPath() + "quasselCert.pem");
158                 if (success)
159                     qWarning() << "*   Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
160                 else
161                     qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
162             }
163 #endif /* !Q_OS_MAC */
164             qWarning() << "*** Migration completed.\n\n";
165         }
166     }
167     // MIGRATION end
168
169     // check settings version
170     // so far, we only have 1
171     CoreSettings s;
172     if (s.version() != 1) {
173         qCritical() << "Invalid core settings version, terminating!";
174         exit(EXIT_FAILURE);
175     }
176
177     registerStorageBackends();
178     registerAuthenticatorBackends();
179
180     connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
181     _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
182 }
183
184
185 void Core::init()
186 {
187     CoreSettings cs;
188     // legacy
189     QVariantMap dbsettings = cs.storageSettings().toMap();
190     _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap());
191
192     // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work!
193     QVariantMap authSettings = cs.authSettings().toMap();
194     initAuthenticator(authSettings.value("AuthBackend").toString(), authSettings.value("ConnectionProperties").toMap());
195
196     if (Quassel::isOptionSet("select-backend")) {
197         selectBackend(Quassel::optionValue("select-backend"));
198         exit(0);
199     }
200
201     // TODO: add --select-authenticator command line option and code.
202
203     if (!_configured) {
204         if (!_storageBackends.count()) {
205             qWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
206             qWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
207                                         "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
208                                         "to work."));
209             exit(1); // TODO make this less brutal (especially for mono client -> popup)
210         }
211
212         qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
213
214         if (!cs.isWritable()) {
215             qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
216             exit(EXIT_FAILURE);
217         }
218
219     }
220
221     if (Quassel::isOptionSet("add-user")) {
222         exit(createUser() ? EXIT_SUCCESS : EXIT_FAILURE);
223
224     }
225
226     if (Quassel::isOptionSet("change-userpass")) {
227         exit(changeUserPass(Quassel::optionValue("change-userpass")) ?
228                        EXIT_SUCCESS : EXIT_FAILURE);
229     }
230
231     connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
232     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
233     if (!startListening()) exit(1);  // TODO make this less brutal
234
235     if (Quassel::isOptionSet("oidentd"))
236         _oidentdConfigGenerator = new OidentdConfigGenerator(this);
237 }
238
239
240 Core::~Core()
241 {
242     // FIXME do we need more cleanup for handlers?
243     foreach(CoreAuthHandler *handler, _connectingClients) {
244         handler->deleteLater(); // disconnect non authed clients
245     }
246     qDeleteAll(_sessions);
247     qDeleteAll(_storageBackends);
248 }
249
250
251 /*** Session Restore ***/
252
253 void Core::saveState()
254 {
255     CoreSettings s;
256     QVariantMap state;
257     QVariantList activeSessions;
258     foreach(UserId user, instance()->_sessions.keys())
259         activeSessions << QVariant::fromValue<UserId>(user);
260     state["CoreStateVersion"] = 1;
261     state["ActiveSessions"] = activeSessions;
262     s.setCoreState(state);
263 }
264
265
266 void Core::restoreState()
267 {
268     if (!instance()->_configured) {
269         // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
270         return;
271     }
272     if (instance()->_sessions.count()) {
273         qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
274         return;
275     }
276     CoreSettings s;
277     /* We don't check, since we are at the first version since switching to Git
278     uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
279     if(statever < 1) {
280       qWarning() << qPrintable(tr("Core state too old, ignoring..."));
281       return;
282     }
283     */
284
285     QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
286     if (activeSessions.count() > 0) {
287         quInfo() << "Restoring previous core state...";
288         foreach(QVariant v, activeSessions) {
289             UserId user = v.value<UserId>();
290             instance()->sessionForUser(user, true);
291         }
292     }
293 }
294
295
296 /*** Core Setup ***/
297
298 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authBackend, const QVariantMap &authSetupData)
299 {
300     return instance()->setupCore(adminUser, adminPassword, backend, setupData, authBackend, authSetupData);
301 }
302
303
304 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authBackend, const QVariantMap &authSetupData)
305 {
306     if (_configured)
307         return tr("Core is already configured! Not configuring again...");
308
309     if (adminUser.isEmpty() || adminPassword.isEmpty()) {
310         return tr("Admin user or password not set.");
311     }
312     if (!(_configured = initStorage(backend, setupData, true))) {
313         return tr("Could not setup storage!");
314     }
315
316     quInfo() << "Selected authenticator: " << authBackend;
317     if (!(_configured = initAuthenticator(authBackend, authSetupData, true)))
318     {
319         return tr("Could not setup authenticator!");
320     }
321
322     if (!saveBackendSettings(backend, setupData)) {
323         return tr("Could not save backend settings, probably a permission problem.");
324     }
325         saveAuthBackendSettings(authBackend, authSetupData);
326
327     quInfo() << qPrintable(tr("Creating admin user..."));
328     _storage->addUser(adminUser, adminPassword);
329     startListening(); // TODO check when we need this
330     return QString();
331 }
332
333
334 QString Core::setupCoreForInternalUsage()
335 {
336     Q_ASSERT(!_storageBackends.isEmpty());
337
338     qsrand(QDateTime::currentDateTime().toTime_t());
339     int pass = 0;
340     for (int i = 0; i < 10; i++) {
341         pass *= 10;
342         pass += qrand() % 10;
343     }
344
345     // mono client currently needs sqlite
346     return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "StorageAuth", QVariantMap());
347 }
348
349
350 /*** Storage Handling ***/
351 void Core::registerStorageBackends()
352 {
353     // Register storage backends here!
354     registerStorageBackend(new SqliteStorage(this));
355     registerStorageBackend(new PostgreSqlStorage(this));
356 }
357
358
359 bool Core::registerStorageBackend(Storage *backend)
360 {
361     if (backend->isAvailable()) {
362         _storageBackends[backend->displayName()] = backend;
363         return true;
364     }
365     else {
366         backend->deleteLater();
367         return false;
368     }
369 }
370
371 void Core::unregisterStorageBackends()
372 {
373     foreach(Storage *s, _storageBackends.values()) {
374         s->deleteLater();
375     }
376     _storageBackends.clear();
377 }
378
379
380 void Core::unregisterStorageBackend(Storage *backend)
381 {
382     _storageBackends.remove(backend->displayName());
383     backend->deleteLater();
384 }
385
386 // Authentication handling, now independent from storage.
387 // Register and unregister authenticators.
388
389 void Core::registerAuthenticatorBackends()
390 {
391     // Register new authentication backends here!
392     registerAuthenticatorBackend(new SqlAuthenticator(this));
393 #ifdef HAVE_LDAP
394     registerAuthenticatorBackend(new LdapAuthenticator(this));
395 #endif
396
397 }
398
399 bool Core::registerAuthenticatorBackend(Authenticator *authenticator)
400 {
401     if (authenticator->isAvailable())
402     {
403         _authenticatorBackends[authenticator->displayName()] = authenticator;
404         return true;
405     } else {
406         authenticator->deleteLater();
407         return false;
408     }
409 }
410
411 void Core::unregisterAuthenticatorBackends()
412 {
413     foreach(Authenticator* a, _authenticatorBackends.values())
414     {
415         a->deleteLater();
416     }
417     _authenticatorBackends.clear();
418 }
419
420 void Core::unregisterAuthenticatorBackend(Authenticator *backend)
421 {
422     _authenticatorBackends.remove(backend->displayName());
423     backend->deleteLater();
424 }
425
426 // old db settings:
427 // "Type" => "sqlite"
428 bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
429 {
430     _storage = 0;
431
432     if (backend.isEmpty()) {
433         return false;
434     }
435
436     Storage *storage = 0;
437     if (_storageBackends.contains(backend)) {
438         storage = _storageBackends[backend];
439     }
440     else {
441         qCritical() << "Selected storage backend is not available:" << backend;
442         return false;
443     }
444
445     Storage::State storageState = storage->init(settings);
446     switch (storageState) {
447     case Storage::NeedsSetup:
448         if (!setup)
449             return false;  // trigger setup process
450         if (storage->setup(settings))
451             return initStorage(backend, settings, false);
452     // if initialization wasn't successful, we quit to keep from coming up unconfigured
453     case Storage::NotAvailable:
454         qCritical() << "FATAL: Selected storage backend is not available:" << backend;
455         exit(EXIT_FAILURE);
456     case Storage::IsReady:
457         // delete all other backends
458         _storageBackends.remove(backend);
459         unregisterStorageBackends();
460         connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
461     }
462     _storage = storage;
463     return true;
464 }
465
466 // XXX: TODO: Apparently, this is legacy?
467 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup)
468 {
469     _authenticator = 0;
470
471     if (backend.isEmpty()) {
472             return false;
473     }
474
475     Authenticator *authenticator = 0;
476     if (_authenticatorBackends.contains(backend)) {
477         authenticator = _authenticatorBackends[backend];
478     }
479     else {
480         qCritical() << "Selected auth backend is not available:" << backend;
481         return false;
482     }
483
484     Authenticator::State authState = authenticator->init(settings);
485     switch (authState) {
486     case Authenticator::NeedsSetup:
487         if (!setup)
488             return false;  // trigger setup process
489         if (authenticator->setup(settings))
490             return initAuthenticator(backend, settings, false);
491     // if initialization wasn't successful, we quit to keep from coming up unconfigured
492     case Authenticator::NotAvailable:
493         qCritical() << "FATAL: Selected auth backend is not available:" << backend;
494         exit(EXIT_FAILURE);
495     case Authenticator::IsReady:
496         // delete all other backends
497         _authenticatorBackends.remove(backend);
498         unregisterAuthenticatorBackends();
499     }
500     _authenticator = authenticator;
501     return true;
502 }
503
504 void Core::syncStorage()
505 {
506     if (_storage)
507         _storage->sync();
508 }
509
510
511 /*** Storage Access ***/
512 bool Core::createNetwork(UserId user, NetworkInfo &info)
513 {
514     NetworkId networkId = instance()->_storage->createNetwork(user, info);
515     if (!networkId.isValid())
516         return false;
517
518     info.networkId = networkId;
519     return true;
520 }
521
522
523 /*** Network Management ***/
524
525 bool Core::sslSupported()
526 {
527 #ifdef HAVE_SSL
528     SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
529     return sslServer && sslServer->isCertValid();
530 #else
531     return false;
532 #endif
533 }
534
535
536 bool Core::reloadCerts()
537 {
538 #ifdef HAVE_SSL
539     SslServer *sslServerv4 = qobject_cast<SslServer *>(&instance()->_server);
540     bool retv4 = sslServerv4->reloadCerts();
541
542     SslServer *sslServerv6 = qobject_cast<SslServer *>(&instance()->_v6server);
543     bool retv6 = sslServerv6->reloadCerts();
544
545     return retv4 && retv6;
546 #else
547     // SSL not supported, don't mark configuration reload as failed
548     return true;
549 #endif
550 }
551
552
553 bool Core::startListening()
554 {
555     // in mono mode we only start a local port if a port is specified in the cli call
556     if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
557         return true;
558
559     bool success = false;
560     uint port = Quassel::optionValue("port").toUInt();
561
562     const QString listen = Quassel::optionValue("listen");
563     const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
564     if (listen_list.size() > 0) {
565         foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
566             QHostAddress addr;
567             if (!addr.setAddress(listen_term)) {
568                 qCritical() << qPrintable(
569                     tr("Invalid listen address %1")
570                     .arg(listen_term)
571                     );
572             }
573             else {
574                 switch (addr.protocol()) {
575                 case QAbstractSocket::IPv6Protocol:
576                     if (_v6server.listen(addr, port)) {
577                         quInfo() << qPrintable(
578                             tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
579                             .arg(addr.toString())
580                             .arg(_v6server.serverPort())
581                             .arg(Quassel::buildInfo().protocolVersion)
582                             );
583                         success = true;
584                     }
585                     else
586                         quWarning() << qPrintable(
587                             tr("Could not open IPv6 interface %1:%2: %3")
588                             .arg(addr.toString())
589                             .arg(port)
590                             .arg(_v6server.errorString()));
591                     break;
592                 case QAbstractSocket::IPv4Protocol:
593                     if (_server.listen(addr, port)) {
594                         quInfo() << qPrintable(
595                             tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
596                             .arg(addr.toString())
597                             .arg(_server.serverPort())
598                             .arg(Quassel::buildInfo().protocolVersion)
599                             );
600                         success = true;
601                     }
602                     else {
603                         // if v6 succeeded on Any, the port will be already in use - don't display the error then
604                         if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
605                             quWarning() << qPrintable(
606                                 tr("Could not open IPv4 interface %1:%2: %3")
607                                 .arg(addr.toString())
608                                 .arg(port)
609                                 .arg(_server.errorString()));
610                     }
611                     break;
612                 default:
613                     qCritical() << qPrintable(
614                         tr("Invalid listen address %1, unknown network protocol")
615                         .arg(listen_term)
616                         );
617                     break;
618                 }
619             }
620         }
621     }
622     if (!success)
623         quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
624
625     return success;
626 }
627
628
629 void Core::stopListening(const QString &reason)
630 {
631     bool wasListening = false;
632     if (_server.isListening()) {
633         wasListening = true;
634         _server.close();
635     }
636     if (_v6server.isListening()) {
637         wasListening = true;
638         _v6server.close();
639     }
640     if (wasListening) {
641         if (reason.isEmpty())
642             quInfo() << "No longer listening for GUI clients.";
643         else
644             quInfo() << qPrintable(reason);
645     }
646 }
647
648
649 void Core::incomingConnection()
650 {
651     QTcpServer *server = qobject_cast<QTcpServer *>(sender());
652     Q_ASSERT(server);
653     while (server->hasPendingConnections()) {
654         QTcpSocket *socket = server->nextPendingConnection();
655
656         CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
657         _connectingClients.insert(handler);
658
659         connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
660         connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
661         connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
662
663         quInfo() << qPrintable(tr("Client connected from"))  << qPrintable(socket->peerAddress().toString());
664
665         if (!_configured) {
666             stopListening(tr("Closing server for basic setup."));
667         }
668     }
669 }
670
671
672 // Potentially called during the initialization phase (before handing the connection off to the session)
673 void Core::clientDisconnected()
674 {
675     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
676     Q_ASSERT(handler);
677
678     quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
679     _connectingClients.remove(handler);
680     handler->deleteLater();
681
682     // make server listen again if still not configured
683     if (!_configured) {
684         startListening();
685     }
686
687     // TODO remove unneeded sessions - if necessary/possible...
688     // Suggestion: kill sessions if they are not connected to any network and client.
689 }
690
691
692 void Core::setupClientSession(RemotePeer *peer, UserId uid)
693 {
694     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
695     Q_ASSERT(handler);
696
697     // From now on everything is handled by the client session
698     disconnect(handler, 0, this, 0);
699     _connectingClients.remove(handler);
700     handler->deleteLater();
701
702     // Find or create session for validated user
703     sessionForUser(uid);
704
705     // as we are currently handling an event triggered by incoming data on this socket
706     // it is unsafe to directly move the socket to the client thread.
707     QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
708 }
709
710
711 void Core::customEvent(QEvent *event)
712 {
713     if (event->type() == AddClientEventId) {
714         AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
715         addClientHelper(addClientEvent->peer, addClientEvent->userId);
716         return;
717     }
718 }
719
720
721 void Core::addClientHelper(RemotePeer *peer, UserId uid)
722 {
723     // Find or create session for validated user
724     SessionThread *session = sessionForUser(uid);
725     session->addClient(peer);
726 }
727
728
729 void Core::setupInternalClientSession(InternalPeer *clientPeer)
730 {
731     if (!_configured) {
732         stopListening();
733         setupCoreForInternalUsage();
734     }
735
736     UserId uid;
737     if (_storage) {
738         uid = _storage->internalUser();
739     }
740     else {
741         qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
742         return;
743     }
744
745     InternalPeer *corePeer = new InternalPeer(this);
746     corePeer->setPeer(clientPeer);
747     clientPeer->setPeer(corePeer);
748
749     // Find or create session for validated user
750     SessionThread *sessionThread = sessionForUser(uid);
751     sessionThread->addClient(corePeer);
752 }
753
754
755 SessionThread *Core::sessionForUser(UserId uid, bool restore)
756 {
757     if (_sessions.contains(uid))
758         return _sessions[uid];
759
760     SessionThread *session = new SessionThread(uid, restore, this);
761     _sessions[uid] = session;
762     session->start();
763     return session;
764 }
765
766
767 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
768 {
769     qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
770 }
771
772
773 QVariantList Core::backendInfo()
774 {
775     QVariantList backends;
776     foreach(const Storage *backend, instance()->_storageBackends.values()) {
777         QVariantMap v;
778         v["DisplayName"] = backend->displayName();
779         v["Description"] = backend->description();
780         v["SetupKeys"] = backend->setupKeys();
781         v["SetupDefaults"] = backend->setupDefaults();
782         v["IsDefault"] = isStorageBackendDefault(backend);
783         backends.append(v);
784     }
785     return backends;
786 }
787
788 QVariantList Core::authenticatorInfo()
789 {
790     QVariantList backends;
791     foreach(const Authenticator *backend, instance()->_authenticatorBackends.values()) {
792         QVariantMap v;
793         v["DisplayName"] = backend->displayName();
794         v["Description"] = backend->description();
795         v["SetupKeys"] = backend->setupKeys();
796         v["SetupDefaults"] = backend->setupDefaults();
797         backends.append(v);
798     }
799     return backends;
800 }
801
802 // migration / backend selection
803 bool Core::selectBackend(const QString &backend)
804 {
805     // reregister all storage backends
806     registerStorageBackends();
807     if (!_storageBackends.contains(backend)) {
808         qWarning() << qPrintable(QString("Core::selectBackend(): unsupported backend: %1").arg(backend));
809         qWarning() << "    supported backends are:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
810         return false;
811     }
812
813     Storage *storage = _storageBackends[backend];
814     QVariantMap settings = promptForSettings(storage);
815
816     Storage::State storageState = storage->init(settings);
817     switch (storageState) {
818     case Storage::IsReady:
819         if (!saveBackendSettings(backend, settings)) {
820             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
821         }
822         qWarning() << "Switched backend to:" << qPrintable(backend);
823         qWarning() << "Backend already initialized. Skipping Migration";
824         return true;
825     case Storage::NotAvailable:
826         qCritical() << "Backend is not available:" << qPrintable(backend);
827         return false;
828     case Storage::NeedsSetup:
829         if (!storage->setup(settings)) {
830             qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
831             return false;
832         }
833
834         if (storage->init(settings) != Storage::IsReady) {
835             qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
836             return false;
837         }
838
839         if (!saveBackendSettings(backend, settings)) {
840             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
841         }
842         qWarning() << "Switched backend to:" << qPrintable(backend);
843         break;
844     }
845
846     // let's see if we have a current storage object we can migrate from
847     AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
848     AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
849     if (reader && writer) {
850         qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
851         delete _storage;
852         _storage = 0;
853         delete storage;
854         storage = 0;
855         if (reader->migrateTo(writer)) {
856             qDebug() << "Migration finished!";
857             if (!saveBackendSettings(backend, settings)) {
858                 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
859                 return false;
860             }
861             return true;
862         }
863         return false;
864         qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
865     }
866
867     // inform the user why we cannot merge
868     if (!_storage) {
869         qWarning() << "No currently active backend. Skipping migration.";
870     }
871     else if (!reader) {
872         qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
873     }
874     if (writer) {
875         qWarning() << "New backend does not support migration:" << qPrintable(backend);
876     }
877
878     // so we were unable to merge, but let's create a user \o/
879     _storage = storage;
880     createUser();
881     return true;
882 }
883
884
885 bool Core::createUser()
886 {
887     QTextStream out(stdout);
888     QTextStream in(stdin);
889     out << "Add a new user:" << endl;
890     out << "Username: ";
891     out.flush();
892     QString username = in.readLine().trimmed();
893
894     disableStdInEcho();
895     out << "Password: ";
896     out.flush();
897     QString password = in.readLine().trimmed();
898     out << endl;
899     out << "Repeat Password: ";
900     out.flush();
901     QString password2 = in.readLine().trimmed();
902     out << endl;
903     enableStdInEcho();
904
905     if (password != password2) {
906         qWarning() << "Passwords don't match!";
907         return false;
908     }
909     if (password.isEmpty()) {
910         qWarning() << "Password is empty!";
911         return false;
912     }
913
914     if (_configured && _storage->addUser(username, password).isValid()) {
915         out << "Added user " << username << " successfully!" << endl;
916         return true;
917     }
918     else {
919         qWarning() << "Unable to add user:" << qPrintable(username);
920         return false;
921     }
922 }
923
924
925 bool Core::changeUserPass(const QString &username)
926 {
927     QTextStream out(stdout);
928     QTextStream in(stdin);
929     UserId userId = _storage->getUserId(username);
930     if (!userId.isValid()) {
931         out << "User " << username << " does not exist." << endl;
932         return false;
933     }
934
935     out << "Change password for user: " << username << endl;
936
937     disableStdInEcho();
938     out << "New Password: ";
939     out.flush();
940     QString password = in.readLine().trimmed();
941     out << endl;
942     out << "Repeat Password: ";
943     out.flush();
944     QString password2 = in.readLine().trimmed();
945     out << endl;
946     enableStdInEcho();
947
948     if (password != password2) {
949         qWarning() << "Passwords don't match!";
950         return false;
951     }
952     if (password.isEmpty()) {
953         qWarning() << "Password is empty!";
954         return false;
955     }
956
957     if (_configured && _storage->updateUser(userId, password)) {
958         out << "Password changed successfully!" << endl;
959         return true;
960     }
961     else {
962         qWarning() << "Failed to change password!";
963         return false;
964     }
965 }
966
967
968 bool Core::changeUserPassword(UserId userId, const QString &password)
969 {
970     if (!isConfigured() || !userId.isValid())
971         return false;
972
973     return instance()->_storage->updateUser(userId, password);
974 }
975
976
977 AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
978 {
979     if (!storage)
980         return 0;
981
982     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
983     if (!sqlStorage) {
984         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
985         return 0;
986     }
987
988     return sqlStorage->createMigrationReader();
989 }
990
991
992 AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
993 {
994     if (!storage)
995         return 0;
996
997     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
998     if (!sqlStorage) {
999         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1000         return 0;
1001     }
1002
1003     return sqlStorage->createMigrationWriter();
1004 }
1005
1006
1007 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1008 {
1009     QVariantMap dbsettings;
1010     dbsettings["Backend"] = backend;
1011     dbsettings["ConnectionProperties"] = settings;
1012     CoreSettings s = CoreSettings();
1013     s.setStorageSettings(dbsettings);
1014     return s.sync();
1015 }
1016
1017 void Core::saveAuthBackendSettings(const QString &backend, const QVariantMap &settings)
1018 {
1019     QVariantMap dbsettings;
1020     dbsettings["AuthBackend"] = backend;
1021     dbsettings["ConnectionProperties"] = settings;
1022     CoreSettings().setAuthSettings(dbsettings);
1023 }
1024
1025
1026 QVariantMap Core::promptForSettings(const Storage *storage)
1027 {
1028     QVariantMap settings;
1029
1030     QStringList keys = storage->setupKeys();
1031     if (keys.isEmpty())
1032         return settings;
1033
1034     QTextStream out(stdout);
1035     QTextStream in(stdin);
1036     out << "Default values are in brackets" << endl;
1037
1038     QVariantMap defaults = storage->setupDefaults();
1039     QString value;
1040     foreach(QString key, keys) {
1041         QVariant val;
1042         if (defaults.contains(key)) {
1043             val = defaults[key];
1044         }
1045         out << key;
1046         if (!val.toString().isEmpty()) {
1047             out << " (" << val.toString() << ")";
1048         }
1049         out << ": ";
1050         out.flush();
1051
1052         bool noEcho = QString("password").toLower().startsWith(key.toLower());
1053         if (noEcho) {
1054             disableStdInEcho();
1055         }
1056         value = in.readLine().trimmed();
1057         if (noEcho) {
1058             out << endl;
1059             enableStdInEcho();
1060         }
1061
1062         if (!value.isEmpty()) {
1063             switch (defaults[key].type()) {
1064             case QVariant::Int:
1065                 val = QVariant(value.toInt());
1066                 break;
1067             default:
1068                 val = QVariant(value);
1069             }
1070         }
1071         settings[key] = val;
1072     }
1073     return settings;
1074 }
1075
1076
1077 #ifdef Q_OS_WIN
1078 void Core::stdInEcho(bool on)
1079 {
1080     HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1081     DWORD mode = 0;
1082     GetConsoleMode(hStdin, &mode);
1083     if (on)
1084         mode |= ENABLE_ECHO_INPUT;
1085     else
1086         mode &= ~ENABLE_ECHO_INPUT;
1087     SetConsoleMode(hStdin, mode);
1088 }
1089
1090
1091 #else
1092 void Core::stdInEcho(bool on)
1093 {
1094     termios t;
1095     tcgetattr(STDIN_FILENO, &t);
1096     if (on)
1097         t.c_lflag |= ECHO;
1098     else
1099         t.c_lflag &= ~ECHO;
1100     tcsetattr(STDIN_FILENO, TCSANOW, &t);
1101 }
1102
1103
1104 #endif /* Q_OS_WIN */