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