5dd9721030564d2eab55af658452dca3301d1fa6
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, 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
38 Core *Core::instanceptr = 0;
39
40 Core *Core::instance() {
41   if(instanceptr) return instanceptr;
42   instanceptr = new Core();
43   instanceptr->init();
44   return instanceptr;
45 }
46
47 void Core::destroy() {
48   delete instanceptr;
49   instanceptr = 0;
50 }
51
52 Core::Core()
53   : _storage(0)
54 {
55   _startTime = QDateTime::currentDateTime().toUTC();  // for uptime :)
56
57   Quassel::loadTranslation(QLocale::system());
58
59   // FIXME: MIGRATION 0.3 -> 0.4: Move database and core config to new location
60   // Move settings, note this does not delete the old files
61 #ifdef Q_WS_MAC
62     QSettings newSettings("quassel-irc.org", "quasselcore");
63 #else
64
65 # ifdef Q_WS_WIN
66     QSettings::Format format = QSettings::IniFormat;
67 # else
68     QSettings::Format format = QSettings::NativeFormat;
69 # endif
70     QString newFilePath = Quassel::configDirPath() + "quasselcore"
71     + ((format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
72     QSettings newSettings(newFilePath, format);
73 #endif /* Q_WS_MAC */
74
75   if(newSettings.value("Config/Version").toUInt() == 0) {
76 #   ifdef Q_WS_MAC
77     QString org = "quassel-irc.org";
78 #   else
79     QString org = "Quassel Project";
80 #   endif
81     QSettings oldSettings(org, "Quassel Core");
82     if(oldSettings.allKeys().count()) {
83       qWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
84       foreach(QString key, oldSettings.allKeys())
85         newSettings.setValue(key, oldSettings.value(key));
86       newSettings.setValue("Config/Version", 1);
87       qWarning() << "*   Your core settings have been migrated to" << newSettings.fileName();
88
89 #ifndef Q_WS_MAC /* we don't need to move the db and cert for mac */
90 #ifdef Q_OS_WIN32
91       QString quasselDir = qgetenv("APPDATA") + "/quassel/";
92 #elif defined Q_WS_MAC
93       QString quasselDir = QDir::homePath() + "/Library/Application Support/Quassel/";
94 #else
95       QString quasselDir = QDir::homePath() + "/.quassel/";
96 #endif
97
98       QFileInfo info(Quassel::configDirPath() + "quassel-storage.sqlite");
99       if(!info.exists()) {
100       // move database, if we found it
101         QFile oldDb(quasselDir + "quassel-storage.sqlite");
102         if(oldDb.exists()) {
103           bool success = oldDb.rename(Quassel::configDirPath() + "quassel-storage.sqlite");
104           if(success)
105             qWarning() << "*   Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
106           else
107             qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
108         }
109       }
110       // move certificate
111       QFileInfo certInfo(quasselDir + "quasselCert.pem");
112       if(certInfo.exists()) {
113         QFile cert(quasselDir + "quasselCert.pem");
114         bool success = cert.rename(Quassel::configDirPath() + "quasselCert.pem");
115         if(success)
116           qWarning() << "*   Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
117         else
118           qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
119       }
120 #endif /* !Q_WS_MAC */
121       qWarning() << "*** Migration completed.\n\n";
122     }
123   }
124   // MIGRATION end
125
126   // check settings version
127   // so far, we only have 1
128   CoreSettings s;
129   if(s.version() != 1) {
130     qCritical() << "Invalid core settings version, terminating!";
131     exit(EXIT_FAILURE);
132   }
133
134   // Register storage backends here!
135   registerStorageBackend(new SqliteStorage(this));
136   registerStorageBackend(new PostgreSqlStorage(this));
137
138   connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
139   _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
140 }
141
142 void Core::init() {
143   CoreSettings cs2;
144   QVariantMap connectionProperties = cs2.storageSettings().toMap()["ConnectionProperties"].toMap();
145   qDebug() << connectionProperties;
146   SqliteMigrationReader *reader = new SqliteMigrationReader();
147   qDebug() << "reader:" << reader->init();
148   PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
149   qDebug() << "writer:" << writer->init(connectionProperties);
150   qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(reader->displayName(), writer->displayName()));
151   if(reader->migrateTo(writer))
152     qDebug() << "Migration finished!";
153   return;
154
155
156
157
158
159
160
161
162
163
164   
165   CoreSettings cs;
166   _configured = initStorage(cs.storageSettings().toMap());
167
168   if(!_configured) {
169     if(!_storageBackends.count()) {
170       qWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
171       qWarning() << qPrintable(tr("Currently, Quassel only supports SQLite3. You need to build your\n"
172                                   "Qt library with the sqlite plugin enabled in order for quasselcore\n"
173                                   "to work."));
174       exit(1); // TODO make this less brutal (especially for mono client -> popup)
175     }
176     qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
177   }
178
179   connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
180   connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
181   if(!startListening()) exit(1); // TODO make this less brutal
182 }
183
184 Core::~Core() {
185   foreach(QTcpSocket *socket, blocksizes.keys()) {
186     socket->disconnectFromHost();  // disconnect non authed clients
187   }
188   qDeleteAll(sessions);
189   qDeleteAll(_storageBackends);
190 }
191
192 /*** Session Restore ***/
193
194 void Core::saveState() {
195   CoreSettings s;
196   QVariantMap state;
197   QVariantList activeSessions;
198   foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
199   state["CoreStateVersion"] = 1;
200   state["ActiveSessions"] = activeSessions;
201   s.setCoreState(state);
202 }
203
204 void Core::restoreState() {
205   if(!instance()->_configured) {
206     // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
207     return;
208   }
209   if(instance()->sessions.count()) {
210     qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
211     return;
212   }
213   CoreSettings s;
214   /* We don't check, since we are at the first version since switching to Git
215   uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
216   if(statever < 1) {
217     qWarning() << qPrintable(tr("Core state too old, ignoring..."));
218     return;
219   }
220   */
221   QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
222   if(activeSessions.count() > 0) {
223     quInfo() << "Restoring previous core state...";
224     foreach(QVariant v, activeSessions) {
225       UserId user = v.value<UserId>();
226       instance()->createSession(user, true);
227     }
228   }
229 }
230
231 /*** Core Setup ***/
232 QString Core::setupCoreForInternalUsage() {
233   Q_ASSERT(!_storageBackends.isEmpty());
234   QVariantMap setupData;
235   qsrand(QDateTime::currentDateTime().toTime_t());
236   int pass = 0;
237   for(int i = 0; i < 10; i++) {
238     pass *= 10;
239     pass += qrand() % 10;
240   }
241   setupData["AdminUser"] = "AdminUser";
242   setupData["AdminPasswd"] = QString::number(pass);
243   setupData["Backend"] = _storageBackends[_storageBackends.keys().first()]->displayName();
244   return setupCore(setupData);
245 }
246
247 QString Core::setupCore(QVariantMap setupData) {
248   QString user = setupData.take("AdminUser").toString();
249   QString password = setupData.take("AdminPasswd").toString();
250   if(user.isEmpty() || password.isEmpty()) {
251     return tr("Admin user or password not set.");
252   }
253   _configured = initStorage(setupData, true);
254   if(!_configured) {
255     return tr("Could not setup storage!");
256   }
257   CoreSettings s;
258   s.setStorageSettings(setupData);
259   quInfo() << qPrintable(tr("Creating admin user..."));
260   _storage->addUser(user, password);
261   startListening();  // TODO check when we need this
262   return QString();
263 }
264
265 /*** Storage Handling ***/
266
267 bool Core::registerStorageBackend(Storage *backend) {
268   if(backend->isAvailable()) {
269     _storageBackends[backend->displayName()] = backend;
270     return true;
271   } else {
272     backend->deleteLater();
273     return false;
274   }
275 }
276
277 void Core::unregisterStorageBackend(Storage *backend) {
278   _storageBackends.remove(backend->displayName());
279   backend->deleteLater();
280 }
281
282 // old db settings:
283 // "Type" => "sqlite"
284 bool Core::initStorage(QVariantMap dbSettings, bool setup) {
285   _storage = 0;
286
287   QString backend = dbSettings["Backend"].toString();
288   if(backend.isEmpty()) {
289     return false;
290   }
291
292   Storage *storage = 0;
293   if(_storageBackends.contains(backend)) {
294     storage = _storageBackends[backend];
295   } else {
296     qCritical() << "Selected storage backend is not available:" << backend;
297     return false;
298   }
299
300   QVariantMap connectionProperties = dbSettings["ConnectionProperties"].toMap();
301
302   Storage::State storageState = storage->init(connectionProperties);
303   switch(storageState) {
304   case Storage::NeedsSetup:
305     if(!setup)
306       return false; // trigger setup process
307     if(storage->setup(connectionProperties))
308       return initStorage(dbSettings, false);
309     // if setup wasn't successfull we mark the backend as unavailable
310   case Storage::NotAvailable:
311     qCritical() << "Selected storage backend is not available:" << backend;
312     storage->deleteLater();
313     _storageBackends.remove(backend);
314     storage = 0;
315     return false;
316   case Storage::IsReady:
317     // delete all other backends
318     foreach(Storage *s, _storageBackends.values()) {
319       if(s != storage) s->deleteLater();
320     }
321     _storageBackends.clear();
322     connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
323   }
324   _storage = storage;
325   return true;
326 }
327
328 void Core::syncStorage() {
329   if(_storage)
330     _storage->sync();
331 }
332
333 /*** Storage Access ***/
334 bool Core::createNetwork(UserId user, NetworkInfo &info) {
335   NetworkId networkId = instance()->_storage->createNetwork(user, info);
336   if(!networkId.isValid())
337     return false;
338
339   info.networkId = networkId;
340   return true;
341 }
342
343 /*** Network Management ***/
344
345 bool Core::startListening() {
346   // in mono mode we only start a local port if a port is specified in the cli call
347   if(Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
348     return true;
349
350   bool success = false;
351   uint port = Quassel::optionValue("port").toUInt();
352
353   const QString listen = Quassel::optionValue("listen");
354   const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
355   if(listen_list.size() > 0) {
356     foreach (const QString listen_term, listen_list) {  // TODO: handle multiple interfaces for same TCP version gracefully
357       QHostAddress addr;
358       if(!addr.setAddress(listen_term)) {
359         qCritical() << qPrintable(
360           tr("Invalid listen address %1")
361             .arg(listen_term)
362         );
363       } else {
364         switch(addr.protocol()) {
365           case QAbstractSocket::IPv4Protocol:
366             if(_server.listen(addr, port)) {
367               quInfo() << qPrintable(
368                 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
369                   .arg(addr.toString())
370                   .arg(_server.serverPort())
371                   .arg(Quassel::buildInfo().protocolVersion)
372               );
373               success = true;
374             } else
375               quWarning() << qPrintable(
376                 tr("Could not open IPv4 interface %1:%2: %3")
377                   .arg(addr.toString())
378                   .arg(port)
379                   .arg(_server.errorString()));
380             break;
381           case QAbstractSocket::IPv6Protocol:
382             if(_v6server.listen(addr, port)) {
383               quInfo() << qPrintable(
384                 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
385                   .arg(addr.toString())
386                   .arg(_v6server.serverPort())
387                   .arg(Quassel::buildInfo().protocolVersion)
388               );
389               success = true;
390             } else {
391               // if v4 succeeded on Any, the port will be already in use - don't display the error then
392               // FIXME: handle this more sanely, make sure we can listen to both v4 and v6 by default!
393               if(!success || _v6server.serverError() != QAbstractSocket::AddressInUseError)
394                 quWarning() << qPrintable(
395                   tr("Could not open IPv6 interface %1:%2: %3")
396                   .arg(addr.toString())
397                   .arg(port)
398                   .arg(_v6server.errorString()));
399             }
400             break;
401           default:
402             qCritical() << qPrintable(
403               tr("Invalid listen address %1, unknown network protocol")
404                   .arg(listen_term)
405             );
406             break;
407         }
408       }
409     }
410   }
411   if(!success)
412     quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
413
414   return success;
415 }
416
417 void Core::stopListening(const QString &reason) {
418   bool wasListening = false;
419   if(_server.isListening()) {
420     wasListening = true;
421     _server.close();
422   }
423   if(_v6server.isListening()) {
424     wasListening = true;
425     _v6server.close();
426   }
427   if(wasListening) {
428     if(reason.isEmpty())
429       quInfo() << "No longer listening for GUI clients.";
430     else
431       quInfo() << qPrintable(reason);
432   }
433 }
434
435 void Core::incomingConnection() {
436   QTcpServer *server = qobject_cast<QTcpServer *>(sender());
437   Q_ASSERT(server);
438   while(server->hasPendingConnections()) {
439     QTcpSocket *socket = server->nextPendingConnection();
440     connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
441     connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
442     connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
443
444     QVariantMap clientInfo;
445     blocksizes.insert(socket, (quint32)0);
446     quInfo() << qPrintable(tr("Client connected from"))  << qPrintable(socket->peerAddress().toString());
447
448     if(!_configured) {
449       stopListening(tr("Closing server for basic setup."));
450     }
451   }
452 }
453
454 void Core::clientHasData() {
455   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
456   Q_ASSERT(socket && blocksizes.contains(socket));
457   QVariant item;
458   while(SignalProxy::readDataFromDevice(socket, blocksizes[socket], item)) {
459     QVariantMap msg = item.toMap();
460     processClientMessage(socket, msg);
461     if(!blocksizes.contains(socket)) break;  // this socket is no longer ours to handle!
462   }
463 }
464
465 void Core::processClientMessage(QTcpSocket *socket, const QVariantMap &msg) {
466   if(!msg.contains("MsgType")) {
467     // Client is way too old, does not even use the current init format
468     qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
469     socket->close();
470     return;
471   }
472   // OK, so we have at least an init message format we can understand
473   if(msg["MsgType"] == "ClientInit") {
474     QVariantMap reply;
475
476     // Just version information -- check it!
477     uint ver = msg["ProtocolVersion"].toUInt();
478     if(ver < Quassel::buildInfo().coreNeedsProtocol) {
479       reply["MsgType"] = "ClientInitReject";
480       reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
481       "This core needs at least client/core protocol version %1.<br>"
482       "Please consider upgrading your client.").arg(Quassel::buildInfo().coreNeedsProtocol);
483       SignalProxy::writeDataToDevice(socket, reply);
484       qWarning() << qPrintable(tr("Client")) << qPrintable(socket->peerAddress().toString()) << qPrintable(tr("too old, rejecting."));
485       socket->close(); return;
486     }
487
488     reply["CoreVersion"] = Quassel::buildInfo().fancyVersionString;
489     reply["CoreDate"] = Quassel::buildInfo().buildDate;
490     reply["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
491     // TODO: Make the core info configurable
492     int uptime = startTime().secsTo(QDateTime::currentDateTime().toUTC());
493     int updays = uptime / 86400; uptime %= 86400;
494     int uphours = uptime / 3600; uptime %= 3600;
495     int upmins = uptime / 60;
496     reply["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
497                            "Built: %2<br>"
498                            "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
499                                                      .arg(Quassel::buildInfo().buildDate)
500       .arg(updays).arg(uphours,2,10,QChar('0')).arg(upmins,2,10,QChar('0')).arg(startTime().toString(Qt::TextDate));
501
502 #ifdef HAVE_SSL
503     SslServer *sslServer = qobject_cast<SslServer *>(&_server);
504     QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
505     bool supportSsl = (bool)sslServer && (bool)sslSocket && sslServer->isCertValid();
506 #else
507     bool supportSsl = false;
508 #endif
509
510 #ifndef QT_NO_COMPRESS
511     bool supportsCompression = true;
512 #else
513     bool supportsCompression = false;
514 #endif
515
516     reply["SupportSsl"] = supportSsl;
517     reply["SupportsCompression"] = supportsCompression;
518     // switch to ssl/compression after client has been informed about our capabilities (see below)
519
520     reply["LoginEnabled"] = true;
521
522     // check if we are configured, start wizard otherwise
523     if(!_configured) {
524       reply["Configured"] = false;
525       QList<QVariant> backends;
526       foreach(Storage *backend, _storageBackends.values()) {
527         QVariantMap v;
528         v["DisplayName"] = backend->displayName();
529         v["Description"] = backend->description();
530         v["ConnectionProperties"] = backend->setupKeys();
531         qDebug() << backend->setupKeys();
532         backends.append(v);
533       }
534       reply["StorageBackends"] = backends;
535       reply["LoginEnabled"] = false;
536     } else {
537       reply["Configured"] = true;
538     }
539     clientInfo[socket] = msg; // store for future reference
540     reply["MsgType"] = "ClientInitAck";
541     SignalProxy::writeDataToDevice(socket, reply);
542
543 #ifdef HAVE_SSL
544     // after we told the client that we are ssl capable we switch to ssl mode
545     if(supportSsl && msg["UseSsl"].toBool()) {
546       qDebug() << qPrintable(tr("Starting TLS for Client:"))  << qPrintable(socket->peerAddress().toString());
547       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
548       sslSocket->startServerEncryption();
549     }
550 #endif
551
552 #ifndef QT_NO_COMPRESS
553     if(supportsCompression && msg["UseCompression"].toBool()) {
554       socket->setProperty("UseCompression", true);
555       qDebug() << "Using compression for Client:" << qPrintable(socket->peerAddress().toString());
556     }
557 #endif
558
559   } else {
560     // for the rest, we need an initialized connection
561     if(!clientInfo.contains(socket)) {
562       QVariantMap reply;
563       reply["MsgType"] = "ClientLoginReject";
564       reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
565       SignalProxy::writeDataToDevice(socket, reply);
566       qWarning() << qPrintable(tr("Client")) << qPrintable(socket->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting."));
567       socket->close(); return;
568     }
569     if(msg["MsgType"] == "CoreSetupData") {
570       QVariantMap reply;
571       QString result = setupCore(msg["SetupData"].toMap());
572       if(!result.isEmpty()) {
573         reply["MsgType"] = "CoreSetupReject";
574         reply["Error"] = result;
575       } else {
576         reply["MsgType"] = "CoreSetupAck";
577       }
578       SignalProxy::writeDataToDevice(socket, reply);
579     } else if(msg["MsgType"] == "ClientLogin") {
580       QVariantMap reply;
581       UserId uid = _storage->validateUser(msg["User"].toString(), msg["Password"].toString());
582       if(uid == 0) {
583         reply["MsgType"] = "ClientLoginReject";
584         reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
585         SignalProxy::writeDataToDevice(socket, reply);
586         return;
587       }
588       reply["MsgType"] = "ClientLoginAck";
589       SignalProxy::writeDataToDevice(socket, reply);
590       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()));
591       setupClientSession(socket, uid);
592     }
593   }
594 }
595
596 // Potentially called during the initialization phase (before handing the connection off to the session)
597 void Core::clientDisconnected() {
598   QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
599   if(socket) {
600     // here it's safe to call methods on socket!
601     quInfo() << qPrintable(tr("Non-authed client disconnected.")) << qPrintable(socket->peerAddress().toString());
602     blocksizes.remove(socket);
603     clientInfo.remove(socket);
604     socket->deleteLater();
605   } else {
606     // we have to crawl through the hashes and see if we find a victim to remove
607     qDebug() << qPrintable(tr("Non-authed client disconnected. (socket allready destroyed)"));
608
609     // DO NOT CALL ANY METHODS ON socket!!
610     socket = static_cast<QTcpSocket *>(sender());
611
612     QHash<QTcpSocket *, quint32>::iterator blockSizeIter = blocksizes.begin();
613     while(blockSizeIter != blocksizes.end()) {
614       if(blockSizeIter.key() == socket) {
615         blockSizeIter = blocksizes.erase(blockSizeIter);
616       } else {
617         blockSizeIter++;
618       }
619     }
620
621     QHash<QTcpSocket *, QVariantMap>::iterator clientInfoIter = clientInfo.begin();
622     while(clientInfoIter != clientInfo.end()) {
623       if(clientInfoIter.key() == socket) {
624         clientInfoIter = clientInfo.erase(clientInfoIter);
625       } else {
626         clientInfoIter++;
627       }
628     }
629   }
630
631
632   // make server listen again if still not configured
633   if (!_configured) {
634     startListening();
635   }
636
637   // TODO remove unneeded sessions - if necessary/possible...
638   // Suggestion: kill sessions if they are not connected to any network and client.
639 }
640
641 void Core::setupClientSession(QTcpSocket *socket, UserId uid) {
642   // Find or create session for validated user
643   SessionThread *sess;
644   if(sessions.contains(uid)) sess = sessions[uid];
645   else sess = createSession(uid);
646   // Hand over socket, session then sends state itself
647   disconnect(socket, 0, this, 0);
648   blocksizes.remove(socket);
649   clientInfo.remove(socket);
650   if(!sess) {
651     qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(socket->peerAddress().toString());
652     socket->close();
653   }
654   sess->addClient(socket);
655 }
656
657 void Core::setupInternalClientSession(SignalProxy *proxy) {
658   if(!_configured) {
659     stopListening();
660     setupCoreForInternalUsage();
661   }
662
663   UserId uid = _storage->internalUser();
664
665   // Find or create session for validated user
666   SessionThread *sess;
667   if(sessions.contains(uid))
668     sess = sessions[uid];
669   else
670     sess = createSession(uid);
671   sess->addClient(proxy);
672 }
673
674 SessionThread *Core::createSession(UserId uid, bool restore) {
675   if(sessions.contains(uid)) {
676     qWarning() << "Calling createSession() when a session for the user already exists!";
677     return 0;
678   }
679   SessionThread *sess = new SessionThread(uid, restore, this);
680   sessions[uid] = sess;
681   sess->start();
682   return sess;
683 }
684
685 #ifdef HAVE_SSL
686 void Core::sslErrors(const QList<QSslError> &errors) {
687   Q_UNUSED(errors);
688   QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
689   if(socket)
690     socket->ignoreSslErrors();
691 }
692 #endif
693
694 void Core::socketError(QAbstractSocket::SocketError err) {
695   QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(sender());
696   if(socket && err != QAbstractSocket::RemoteHostClosedError)
697     qWarning() << "Core::socketError()" << socket << err << socket->errorString();
698 }