ad9eab8586ea239bb7e2f6c0c63c10589f762de7
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 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
198         qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
199
200         if (!cs.isWritable()) {
201             qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
202             exit(EXIT_FAILURE);
203         }
204
205     }
206
207     if (Quassel::isOptionSet("add-user")) {
208         exit(createUser() ? EXIT_SUCCESS : EXIT_FAILURE);
209
210     }
211
212     if (Quassel::isOptionSet("change-userpass")) {
213         exit(changeUserPass(Quassel::optionValue("change-userpass")) ?
214                        EXIT_SUCCESS : EXIT_FAILURE);
215     }
216
217     connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
218     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
219     if (!startListening()) exit(1);  // TODO make this less brutal
220
221     if (Quassel::isOptionSet("oidentd"))
222         _oidentdConfigGenerator = new OidentdConfigGenerator(this);
223 }
224
225
226 Core::~Core()
227 {
228     // FIXME do we need more cleanup for handlers?
229     foreach(CoreAuthHandler *handler, _connectingClients) {
230         handler->deleteLater(); // disconnect non authed clients
231     }
232     qDeleteAll(_sessions);
233     qDeleteAll(_storageBackends);
234 }
235
236
237 /*** Session Restore ***/
238
239 void Core::saveState()
240 {
241     CoreSettings s;
242     QVariantMap state;
243     QVariantList activeSessions;
244     foreach(UserId user, instance()->_sessions.keys())
245         activeSessions << QVariant::fromValue<UserId>(user);
246     state["CoreStateVersion"] = 1;
247     state["ActiveSessions"] = activeSessions;
248     s.setCoreState(state);
249 }
250
251
252 void Core::restoreState()
253 {
254     if (!instance()->_configured) {
255         // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
256         return;
257     }
258     if (instance()->_sessions.count()) {
259         qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
260         return;
261     }
262     CoreSettings s;
263     /* We don't check, since we are at the first version since switching to Git
264     uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
265     if(statever < 1) {
266       qWarning() << qPrintable(tr("Core state too old, ignoring..."));
267       return;
268     }
269     */
270
271     QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
272     if (activeSessions.count() > 0) {
273         quInfo() << "Restoring previous core state...";
274         foreach(QVariant v, activeSessions) {
275             UserId user = v.value<UserId>();
276             instance()->sessionForUser(user, true);
277         }
278     }
279 }
280
281
282 /*** Core Setup ***/
283
284 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
285 {
286     return instance()->setupCore(adminUser, adminPassword, backend, setupData);
287 }
288
289
290 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
291 {
292     if (_configured)
293         return tr("Core is already configured! Not configuring again...");
294
295     if (adminUser.isEmpty() || adminPassword.isEmpty()) {
296         return tr("Admin user or password not set.");
297     }
298     if (!(_configured = initStorage(backend, setupData, true))) {
299         return tr("Could not setup storage!");
300     }
301
302     if (!saveBackendSettings(backend, setupData)) {
303         return tr("Could not save backend settings, probably a permission problem.");
304     }
305
306     quInfo() << qPrintable(tr("Creating admin user..."));
307     _storage->addUser(adminUser, adminPassword);
308     startListening(); // TODO check when we need this
309     return QString();
310 }
311
312
313 QString Core::setupCoreForInternalUsage()
314 {
315     Q_ASSERT(!_storageBackends.isEmpty());
316
317     qsrand(QDateTime::currentDateTime().toTime_t());
318     int pass = 0;
319     for (int i = 0; i < 10; i++) {
320         pass *= 10;
321         pass += qrand() % 10;
322     }
323
324     // mono client currently needs sqlite
325     return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap());
326 }
327
328
329 /*** Storage Handling ***/
330 void Core::registerStorageBackends()
331 {
332     // Register storage backends here!
333     registerStorageBackend(new SqliteStorage(this));
334     registerStorageBackend(new PostgreSqlStorage(this));
335 }
336
337
338 bool Core::registerStorageBackend(Storage *backend)
339 {
340     if (backend->isAvailable()) {
341         _storageBackends[backend->displayName()] = backend;
342         return true;
343     }
344     else {
345         backend->deleteLater();
346         return false;
347     }
348 }
349
350
351 void Core::unregisterStorageBackends()
352 {
353     foreach(Storage *s, _storageBackends.values()) {
354         s->deleteLater();
355     }
356     _storageBackends.clear();
357 }
358
359
360 void Core::unregisterStorageBackend(Storage *backend)
361 {
362     _storageBackends.remove(backend->displayName());
363     backend->deleteLater();
364 }
365
366
367 // old db settings:
368 // "Type" => "sqlite"
369 bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
370 {
371     _storage = 0;
372
373     if (backend.isEmpty()) {
374         return false;
375     }
376
377     Storage *storage = 0;
378     if (_storageBackends.contains(backend)) {
379         storage = _storageBackends[backend];
380     }
381     else {
382         qCritical() << "Selected storage backend is not available:" << backend;
383         return false;
384     }
385
386     Storage::State storageState = storage->init(settings);
387     switch (storageState) {
388     case Storage::NeedsSetup:
389         if (!setup)
390             return false;  // trigger setup process
391         if (storage->setup(settings))
392             return initStorage(backend, settings, false);
393     // if initialization wasn't successful, we quit to keep from coming up unconfigured
394     case Storage::NotAvailable:
395         qCritical() << "FATAL: Selected storage backend is not available:" << backend;
396         exit(EXIT_FAILURE);
397     case Storage::IsReady:
398         // delete all other backends
399         _storageBackends.remove(backend);
400         unregisterStorageBackends();
401         connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
402     }
403     _storage = storage;
404     return true;
405 }
406
407
408 void Core::syncStorage()
409 {
410     if (_storage)
411         _storage->sync();
412 }
413
414
415 /*** Storage Access ***/
416 bool Core::createNetwork(UserId user, NetworkInfo &info)
417 {
418     NetworkId networkId = instance()->_storage->createNetwork(user, info);
419     if (!networkId.isValid())
420         return false;
421
422     info.networkId = networkId;
423     return true;
424 }
425
426
427 /*** Network Management ***/
428
429 bool Core::sslSupported()
430 {
431 #ifdef HAVE_SSL
432     SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
433     return sslServer && sslServer->isCertValid();
434 #else
435     return false;
436 #endif
437 }
438
439
440 bool Core::reloadCerts()
441 {
442 #ifdef HAVE_SSL
443     SslServer *sslServerv4 = qobject_cast<SslServer *>(&instance()->_server);
444     bool retv4 = sslServerv4->reloadCerts();
445
446     SslServer *sslServerv6 = qobject_cast<SslServer *>(&instance()->_v6server);
447     bool retv6 = sslServerv6->reloadCerts();
448
449     return retv4 && retv6;
450 #else
451     // SSL not supported, don't mark configuration reload as failed
452     return true;
453 #endif
454 }
455
456
457 bool Core::startListening()
458 {
459     // in mono mode we only start a local port if a port is specified in the cli call
460     if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
461         return true;
462
463     bool success = false;
464     uint port = Quassel::optionValue("port").toUInt();
465
466     const QString listen = Quassel::optionValue("listen");
467     const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
468     if (listen_list.size() > 0) {
469         foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
470             QHostAddress addr;
471             if (!addr.setAddress(listen_term)) {
472                 qCritical() << qPrintable(
473                     tr("Invalid listen address %1")
474                     .arg(listen_term)
475                     );
476             }
477             else {
478                 switch (addr.protocol()) {
479                 case QAbstractSocket::IPv6Protocol:
480                     if (_v6server.listen(addr, port)) {
481                         quInfo() << qPrintable(
482                             tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
483                             .arg(addr.toString())
484                             .arg(_v6server.serverPort())
485                             .arg(Quassel::buildInfo().protocolVersion)
486                             );
487                         success = true;
488                     }
489                     else
490                         quWarning() << qPrintable(
491                             tr("Could not open IPv6 interface %1:%2: %3")
492                             .arg(addr.toString())
493                             .arg(port)
494                             .arg(_v6server.errorString()));
495                     break;
496                 case QAbstractSocket::IPv4Protocol:
497                     if (_server.listen(addr, port)) {
498                         quInfo() << qPrintable(
499                             tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
500                             .arg(addr.toString())
501                             .arg(_server.serverPort())
502                             .arg(Quassel::buildInfo().protocolVersion)
503                             );
504                         success = true;
505                     }
506                     else {
507                         // if v6 succeeded on Any, the port will be already in use - don't display the error then
508                         if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
509                             quWarning() << qPrintable(
510                                 tr("Could not open IPv4 interface %1:%2: %3")
511                                 .arg(addr.toString())
512                                 .arg(port)
513                                 .arg(_server.errorString()));
514                     }
515                     break;
516                 default:
517                     qCritical() << qPrintable(
518                         tr("Invalid listen address %1, unknown network protocol")
519                         .arg(listen_term)
520                         );
521                     break;
522                 }
523             }
524         }
525     }
526     if (!success)
527         quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
528
529     return success;
530 }
531
532
533 void Core::stopListening(const QString &reason)
534 {
535     bool wasListening = false;
536     if (_server.isListening()) {
537         wasListening = true;
538         _server.close();
539     }
540     if (_v6server.isListening()) {
541         wasListening = true;
542         _v6server.close();
543     }
544     if (wasListening) {
545         if (reason.isEmpty())
546             quInfo() << "No longer listening for GUI clients.";
547         else
548             quInfo() << qPrintable(reason);
549     }
550 }
551
552
553 void Core::incomingConnection()
554 {
555     QTcpServer *server = qobject_cast<QTcpServer *>(sender());
556     Q_ASSERT(server);
557     while (server->hasPendingConnections()) {
558         QTcpSocket *socket = server->nextPendingConnection();
559
560         CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
561         _connectingClients.insert(handler);
562
563         connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
564         connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
565         connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
566
567         quInfo() << qPrintable(tr("Client connected from"))  << qPrintable(socket->peerAddress().toString());
568
569         if (!_configured) {
570             stopListening(tr("Closing server for basic setup."));
571         }
572     }
573 }
574
575
576 // Potentially called during the initialization phase (before handing the connection off to the session)
577 void Core::clientDisconnected()
578 {
579     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
580     Q_ASSERT(handler);
581
582     quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
583     _connectingClients.remove(handler);
584     handler->deleteLater();
585
586     // make server listen again if still not configured
587     if (!_configured) {
588         startListening();
589     }
590
591     // TODO remove unneeded sessions - if necessary/possible...
592     // Suggestion: kill sessions if they are not connected to any network and client.
593 }
594
595
596 void Core::setupClientSession(RemotePeer *peer, UserId uid)
597 {
598     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
599     Q_ASSERT(handler);
600
601     // From now on everything is handled by the client session
602     disconnect(handler, 0, this, 0);
603     _connectingClients.remove(handler);
604     handler->deleteLater();
605
606     // Find or create session for validated user
607     sessionForUser(uid);
608
609     // as we are currently handling an event triggered by incoming data on this socket
610     // it is unsafe to directly move the socket to the client thread.
611     QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
612 }
613
614
615 void Core::customEvent(QEvent *event)
616 {
617     if (event->type() == AddClientEventId) {
618         AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
619         addClientHelper(addClientEvent->peer, addClientEvent->userId);
620         return;
621     }
622 }
623
624
625 void Core::addClientHelper(RemotePeer *peer, UserId uid)
626 {
627     // Find or create session for validated user
628     SessionThread *session = sessionForUser(uid);
629     session->addClient(peer);
630 }
631
632
633 void Core::setupInternalClientSession(InternalPeer *clientPeer)
634 {
635     if (!_configured) {
636         stopListening();
637         setupCoreForInternalUsage();
638     }
639
640     UserId uid;
641     if (_storage) {
642         uid = _storage->internalUser();
643     }
644     else {
645         qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
646         return;
647     }
648
649     InternalPeer *corePeer = new InternalPeer(this);
650     corePeer->setPeer(clientPeer);
651     clientPeer->setPeer(corePeer);
652
653     // Find or create session for validated user
654     SessionThread *sessionThread = sessionForUser(uid);
655     sessionThread->addClient(corePeer);
656 }
657
658
659 SessionThread *Core::sessionForUser(UserId uid, bool restore)
660 {
661     if (_sessions.contains(uid))
662         return _sessions[uid];
663
664     SessionThread *session = new SessionThread(uid, restore, this);
665     _sessions[uid] = session;
666     session->start();
667     return session;
668 }
669
670
671 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
672 {
673     qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
674 }
675
676
677 QVariantList Core::backendInfo()
678 {
679     QVariantList backends;
680     foreach(const Storage *backend, instance()->_storageBackends.values()) {
681         QVariantMap v;
682         v["DisplayName"] = backend->displayName();
683         v["Description"] = backend->description();
684         v["SetupKeys"] = backend->setupKeys();
685         v["SetupDefaults"] = backend->setupDefaults();
686         v["IsDefault"] = isStorageBackendDefault(backend);
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         if (!saveBackendSettings(backend, settings)) {
711             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
712         }
713         qWarning() << "Switched backend to:" << qPrintable(backend);
714         qWarning() << "Backend already initialized. Skipping Migration";
715         return true;
716     case Storage::NotAvailable:
717         qCritical() << "Backend is not available:" << qPrintable(backend);
718         return false;
719     case Storage::NeedsSetup:
720         if (!storage->setup(settings)) {
721             qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
722             return false;
723         }
724
725         if (storage->init(settings) != Storage::IsReady) {
726             qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
727             return false;
728         }
729
730         if (!saveBackendSettings(backend, settings)) {
731             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
732         }
733         qWarning() << "Switched backend to:" << qPrintable(backend);
734         break;
735     }
736
737     // let's see if we have a current storage object we can migrate from
738     AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
739     AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
740     if (reader && writer) {
741         qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
742         delete _storage;
743         _storage = 0;
744         delete storage;
745         storage = 0;
746         if (reader->migrateTo(writer)) {
747             qDebug() << "Migration finished!";
748             if (!saveBackendSettings(backend, settings)) {
749                 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
750                 return false;
751             }
752             return true;
753         }
754         return false;
755         qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
756     }
757
758     // inform the user why we cannot merge
759     if (!_storage) {
760         qWarning() << "No currently active backend. Skipping migration.";
761     }
762     else if (!reader) {
763         qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
764     }
765     if (writer) {
766         qWarning() << "New backend does not support migration:" << qPrintable(backend);
767     }
768
769     // so we were unable to merge, but let's create a user \o/
770     _storage = storage;
771     createUser();
772     return true;
773 }
774
775
776 bool Core::createUser()
777 {
778     QTextStream out(stdout);
779     QTextStream in(stdin);
780     out << "Add a new user:" << endl;
781     out << "Username: ";
782     out.flush();
783     QString username = in.readLine().trimmed();
784
785     disableStdInEcho();
786     out << "Password: ";
787     out.flush();
788     QString password = in.readLine().trimmed();
789     out << endl;
790     out << "Repeat Password: ";
791     out.flush();
792     QString password2 = in.readLine().trimmed();
793     out << endl;
794     enableStdInEcho();
795
796     if (password != password2) {
797         qWarning() << "Passwords don't match!";
798         return false;
799     }
800     if (password.isEmpty()) {
801         qWarning() << "Password is empty!";
802         return false;
803     }
804
805     if (_configured && _storage->addUser(username, password).isValid()) {
806         out << "Added user " << username << " successfully!" << endl;
807         return true;
808     }
809     else {
810         qWarning() << "Unable to add user:" << qPrintable(username);
811         return false;
812     }
813 }
814
815
816 bool Core::changeUserPass(const QString &username)
817 {
818     QTextStream out(stdout);
819     QTextStream in(stdin);
820     UserId userId = _storage->getUserId(username);
821     if (!userId.isValid()) {
822         out << "User " << username << " does not exist." << endl;
823         return false;
824     }
825
826     out << "Change password for user: " << username << endl;
827
828     disableStdInEcho();
829     out << "New Password: ";
830     out.flush();
831     QString password = in.readLine().trimmed();
832     out << endl;
833     out << "Repeat Password: ";
834     out.flush();
835     QString password2 = in.readLine().trimmed();
836     out << endl;
837     enableStdInEcho();
838
839     if (password != password2) {
840         qWarning() << "Passwords don't match!";
841         return false;
842     }
843     if (password.isEmpty()) {
844         qWarning() << "Password is empty!";
845         return false;
846     }
847
848     if (_configured && _storage->updateUser(userId, password)) {
849         out << "Password changed successfully!" << endl;
850         return true;
851     }
852     else {
853         qWarning() << "Failed to change password!";
854         return false;
855     }
856 }
857
858
859 bool Core::changeUserPassword(UserId userId, const QString &password)
860 {
861     if (!isConfigured() || !userId.isValid())
862         return false;
863
864     return instance()->_storage->updateUser(userId, password);
865 }
866
867
868 AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
869 {
870     if (!storage)
871         return 0;
872
873     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
874     if (!sqlStorage) {
875         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
876         return 0;
877     }
878
879     return sqlStorage->createMigrationReader();
880 }
881
882
883 AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
884 {
885     if (!storage)
886         return 0;
887
888     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
889     if (!sqlStorage) {
890         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
891         return 0;
892     }
893
894     return sqlStorage->createMigrationWriter();
895 }
896
897
898 bool Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
899 {
900     QVariantMap dbsettings;
901     dbsettings["Backend"] = backend;
902     dbsettings["ConnectionProperties"] = settings;
903     CoreSettings s = CoreSettings();
904     s.setStorageSettings(dbsettings);
905     return s.sync();
906 }
907
908
909 QVariantMap Core::promptForSettings(const Storage *storage)
910 {
911     QVariantMap settings;
912
913     QStringList keys = storage->setupKeys();
914     if (keys.isEmpty())
915         return settings;
916
917     QTextStream out(stdout);
918     QTextStream in(stdin);
919     out << "Default values are in brackets" << endl;
920
921     QVariantMap defaults = storage->setupDefaults();
922     QString value;
923     foreach(QString key, keys) {
924         QVariant val;
925         if (defaults.contains(key)) {
926             val = defaults[key];
927         }
928         out << key;
929         if (!val.toString().isEmpty()) {
930             out << " (" << val.toString() << ")";
931         }
932         out << ": ";
933         out.flush();
934
935         bool noEcho = QString("password").toLower().startsWith(key.toLower());
936         if (noEcho) {
937             disableStdInEcho();
938         }
939         value = in.readLine().trimmed();
940         if (noEcho) {
941             out << endl;
942             enableStdInEcho();
943         }
944
945         if (!value.isEmpty()) {
946             switch (defaults[key].type()) {
947             case QVariant::Int:
948                 val = QVariant(value.toInt());
949                 break;
950             default:
951                 val = QVariant(value);
952             }
953         }
954         settings[key] = val;
955     }
956     return settings;
957 }
958
959
960 #ifdef Q_OS_WIN
961 void Core::stdInEcho(bool on)
962 {
963     HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
964     DWORD mode = 0;
965     GetConsoleMode(hStdin, &mode);
966     if (on)
967         mode |= ENABLE_ECHO_INPUT;
968     else
969         mode &= ~ENABLE_ECHO_INPUT;
970     SetConsoleMode(hStdin, mode);
971 }
972
973
974 #else
975 void Core::stdInEcho(bool on)
976 {
977     termios t;
978     tcgetattr(STDIN_FILENO, &t);
979     if (on)
980         t.c_lflag |= ECHO;
981     else
982         t.c_lflag &= ~ECHO;
983     tcsetattr(STDIN_FILENO, TCSANOW, &t);
984 }
985
986
987 #endif /* Q_OS_WIN */