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