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