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