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