1 /***************************************************************************
2  *   SPDX-FileCopyrightText: 2006 Till Adam <adam@kde.org>                 *
3  *                                                                         *
4  *   SPDX-License-Identifier: LGPL-2.0-or-later                            *
5  ***************************************************************************/
6 
7 #include "akonadi.h"
8 #include "akonadiserver_debug.h"
9 #include "connection.h"
10 #include "handler.h"
11 #include "serveradaptor.h"
12 
13 #include "aklocalserver.h"
14 #include "cachecleaner.h"
15 #include "debuginterface.h"
16 #include "intervalcheck.h"
17 #include "notificationmanager.h"
18 #include "preprocessormanager.h"
19 #include "resourcemanager.h"
20 #include "search/searchmanager.h"
21 #include "search/searchtaskmanager.h"
22 #include "storage/collectionstatistics.h"
23 #include "storage/datastore.h"
24 #include "storage/dbconfig.h"
25 #include "storage/itemretrievalmanager.h"
26 #include "storagejanitor.h"
27 #include "tracer.h"
28 #include "utils.h"
29 
30 #include <private/dbus_p.h>
31 #include <private/instance_p.h>
32 #include <private/protocol_p.h>
33 #include <private/standarddirs_p.h>
34 
35 #include <QSqlError>
36 #include <QSqlQuery>
37 
38 #include <QCoreApplication>
39 #include <QDBusServiceWatcher>
40 #include <QDir>
41 #include <QSettings>
42 #include <QTimer>
43 
44 using namespace Akonadi;
45 using namespace Akonadi::Server;
46 using namespace std::chrono_literals;
47 namespace
48 {
49 class AkonadiDataStore : public DataStore
50 {
51     Q_OBJECT
52 public:
AkonadiDataStore(AkonadiServer & server)53     explicit AkonadiDataStore(AkonadiServer &server)
54         : DataStore(server)
55     {
56     }
57 };
58 
59 class AkonadiDataStoreFactory : public DataStoreFactory
60 {
61 public:
AkonadiDataStoreFactory(AkonadiServer & akonadi)62     explicit AkonadiDataStoreFactory(AkonadiServer &akonadi)
63         : m_akonadi(akonadi)
64     {
65     }
66 
createStore()67     DataStore *createStore() override
68     {
69         return new AkonadiDataStore(m_akonadi);
70     }
71 
72 private:
73     AkonadiServer &m_akonadi;
74 };
75 
76 } // namespace
77 
AkonadiServer()78 AkonadiServer::AkonadiServer()
79 {
80     // Register bunch of useful types
81     qRegisterMetaType<Protocol::CommandPtr>();
82     qRegisterMetaType<Protocol::ChangeNotificationPtr>();
83     qRegisterMetaType<Protocol::ChangeNotificationList>();
84     qRegisterMetaType<quintptr>("quintptr");
85 
86     DataStore::setFactory(std::make_unique<AkonadiDataStoreFactory>(*this));
87 }
88 
init()89 bool AkonadiServer::init()
90 {
91     qCInfo(AKONADISERVER_LOG) << "Starting up the Akonadi Server...";
92 
93     const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite);
94     QSettings settings(serverConfigFile, QSettings::IniFormat);
95     // Restrict permission to 600, as the file might contain database password in plaintext
96     QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner);
97 
98     const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly);
99     QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat);
100 
101     const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS");
102     if (!dbusAddress.isEmpty()) {
103         connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress));
104     }
105 
106     // Setup database
107     if (!setupDatabase()) {
108         quit();
109         return false;
110     }
111 
112     // Create local servers and start listening
113     if (!createServers(settings, connectionSettings)) {
114         quit();
115         return false;
116     }
117 
118     const auto searchManagers = settings.value(QStringLiteral("Search/Manager"), QStringList{QStringLiteral("Agent")}).toStringList();
119 
120     mTracer = std::make_unique<Tracer>();
121     mCollectionStats = std::make_unique<CollectionStatistics>();
122     mCacheCleaner = std::make_unique<CacheCleaner>();
123     mItemRetrieval = std::make_unique<ItemRetrievalManager>();
124     mAgentSearchManager = std::make_unique<SearchTaskManager>();
125 
126     mDebugInterface = std::make_unique<DebugInterface>(*mTracer);
127     mResourceManager = std::make_unique<ResourceManager>(*mTracer);
128     mPreprocessorManager = std::make_unique<PreprocessorManager>(*mTracer);
129     mIntervalCheck = std::make_unique<IntervalCheck>(*mItemRetrieval);
130     mSearchManager = std::make_unique<SearchManager>(searchManagers, *mAgentSearchManager);
131     mStorageJanitor = std::make_unique<StorageJanitor>(*this);
132 
133     if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) {
134         mPreprocessorManager->setEnabled(false);
135     }
136 
137     new ServerAdaptor(this);
138     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this);
139 
140     mControlWatcher =
141         std::make_unique<QDBusServiceWatcher>(DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration);
142     connect(mControlWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
143         qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!";
144         quit();
145     });
146 
147     // Unhide all the items that are actually hidden.
148     // The hidden flag was probably left out after an (abrupt)
149     // server quit. We don't attempt to resume preprocessing
150     // for the items as we don't actually know at which stage the
151     // operation was interrupted...
152     DataStore::self()->unhideAllPimItems();
153 
154     // We are ready, now register org.freedesktop.Akonadi service to DBus and
155     // the fun can begin
156     if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) {
157         qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message();
158         quit();
159         return false;
160     }
161 
162     return true;
163 }
164 
165 AkonadiServer::~AkonadiServer() = default;
166 
quit()167 bool AkonadiServer::quit()
168 {
169     if (mAlreadyShutdown) {
170         return true;
171     }
172     mAlreadyShutdown = true;
173 
174     qCDebug(AKONADISERVER_LOG) << "terminating connection threads";
175     mConnections.clear();
176 
177     qCDebug(AKONADISERVER_LOG) << "terminating service threads";
178     // Keep this order in sync (reversed) with the order of initialization
179     mStorageJanitor.reset();
180     mSearchManager.reset();
181     mIntervalCheck.reset();
182     mPreprocessorManager.reset();
183     mResourceManager.reset();
184     mDebugInterface.reset();
185 
186     mAgentSearchManager.reset();
187     mItemRetrieval.reset();
188     mCacheCleaner.reset();
189     mCollectionStats.reset();
190     mTracer.reset();
191 
192     if (DbConfig::isConfigured()) {
193         if (DataStore::hasDataStore()) {
194             DataStore::self()->close();
195         }
196         qCDebug(AKONADISERVER_LOG) << "stopping db process";
197         stopDatabaseProcess();
198     }
199 
200     // QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat);
201     const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly);
202 
203     if (!QDir::home().remove(connectionSettingsFile)) {
204         qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file";
205     }
206 
207     QTimer::singleShot(0s, this, &AkonadiServer::doQuit);
208 
209     return true;
210 }
211 
doQuit()212 void AkonadiServer::doQuit()
213 {
214     QCoreApplication::exit();
215 }
216 
newCmdConnection(quintptr socketDescriptor)217 void AkonadiServer::newCmdConnection(quintptr socketDescriptor)
218 {
219     if (mAlreadyShutdown) {
220         return;
221     }
222 
223     auto connection = std::make_unique<Connection>(socketDescriptor, *this);
224     connect(connection.get(), &Connection::disconnected, this, &AkonadiServer::connectionDisconnected);
225     mConnections.push_back(std::move(connection));
226 }
227 
connectionDisconnected()228 void AkonadiServer::connectionDisconnected()
229 {
230     auto it = std::find_if(mConnections.begin(), mConnections.end(), [this](const auto &ptr) {
231         return ptr.get() == sender();
232     });
233     Q_ASSERT(it != mConnections.end());
234     mConnections.erase(it);
235 }
236 
setupDatabase()237 bool AkonadiServer::setupDatabase()
238 {
239     if (!DbConfig::configuredDatabase()) {
240         return false;
241     }
242 
243     if (DbConfig::configuredDatabase()->useInternalServer()) {
244         if (!startDatabaseProcess()) {
245             return false;
246         }
247     } else {
248         if (!createDatabase()) {
249             return false;
250         }
251     }
252 
253     DbConfig::configuredDatabase()->setup();
254 
255     // initialize the database
256     DataStore *db = DataStore::self();
257     if (!db->database().isOpen()) {
258         qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text();
259         return false;
260     }
261     if (!db->init()) {
262         qCCritical(AKONADISERVER_LOG) << "Unable to initialize database.";
263         return false;
264     }
265 
266     return true;
267 }
268 
startDatabaseProcess()269 bool AkonadiServer::startDatabaseProcess()
270 {
271     if (!DbConfig::configuredDatabase()->useInternalServer()) {
272         qCCritical(AKONADISERVER_LOG) << "Trying to start external database!";
273     }
274 
275     // create the database directories if they don't exists
276     StandardDirs::saveDir("data");
277     StandardDirs::saveDir("data", QStringLiteral("file_db_data"));
278 
279     return DbConfig::configuredDatabase()->startInternalServer();
280 }
281 
createDatabase()282 bool AkonadiServer::createDatabase()
283 {
284     bool success = true;
285     const QLatin1String initCon("initConnection");
286     QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
287     DbConfig::configuredDatabase()->apply(db);
288     db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
289     if (!db.isValid()) {
290         qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection";
291         return false;
292     }
293 
294     if (db.open()) {
295         db.close();
296     } else {
297         qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName();
298         qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
299         qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
300 
301         db.close();
302         db.setDatabaseName(QString());
303         if (db.open()) {
304             {
305                 QSqlQuery query(db);
306                 if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) {
307                     qCCritical(AKONADISERVER_LOG) << "Failed to create database";
308                     qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
309                     qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
310                     success = false;
311                 }
312             } // make sure query is destroyed before we close the db
313             db.close();
314         } else {
315             qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
316             qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
317             success = false;
318         }
319     }
320     return success;
321 }
322 
stopDatabaseProcess()323 void AkonadiServer::stopDatabaseProcess()
324 {
325     if (!DbConfig::configuredDatabase()->useInternalServer()) {
326         // closing initConnection this late to work around QTBUG-63108
327         QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
328         return;
329     }
330 
331     DbConfig::configuredDatabase()->stopInternalServer();
332 }
333 
createServers(QSettings & settings,QSettings & connectionSettings)334 bool AkonadiServer::createServers(QSettings &settings, QSettings &connectionSettings)
335 {
336     mCmdServer = std::make_unique<AkLocalServer>(this);
337     connect(mCmdServer.get(), qOverload<quintptr>(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection);
338 
339     mNotificationManager = std::make_unique<NotificationManager>();
340     mNtfServer = std::make_unique<AkLocalServer>(this);
341     // Note: this is a queued connection, as NotificationManager lives in its
342     // own thread
343     connect(mNtfServer.get(), qOverload<quintptr>(&AkLocalServer::newConnection), mNotificationManager.get(), &NotificationManager::registerConnection);
344 
345     // TODO: share socket setup with client
346 #ifdef Q_OS_WIN
347     // use the installation prefix as uid
348     QString suffix;
349     if (Instance::hasIdentifier()) {
350         suffix = QStringLiteral("%1-").arg(Instance::identifier());
351     }
352     suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath()));
353     const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix;
354     const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString();
355     if (!mCmdServer->listen(cmdPipe)) {
356         qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe << ":" << mCmdServer->errorString();
357         return false;
358     }
359 
360     const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix;
361     const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString();
362     if (!mNtfServer->listen(ntfPipe)) {
363         qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe << ":" << mNtfServer->errorString();
364         return false;
365     }
366 
367     connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe"));
368     connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe);
369     connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe"));
370     connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe);
371 #else
372     Q_UNUSED(settings)
373 
374     const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket");
375     const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket");
376     const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"), qMax(cmdSocketName.length(), ntfSocketName.length()));
377     const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName;
378     QFile::remove(cmdSocketFile);
379     if (!mCmdServer->listen(cmdSocketFile)) {
380         qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString();
381         return false;
382     }
383 
384     const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName;
385     QFile::remove(ntfSocketFile);
386     if (!mNtfServer->listen(ntfSocketFile)) {
387         qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString();
388         return false;
389     }
390 
391     connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath"));
392     connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile);
393     connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath"));
394     connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile);
395 #endif
396 
397     return true;
398 }
399 
cacheCleaner()400 CacheCleaner *AkonadiServer::cacheCleaner()
401 {
402     return mCacheCleaner.get();
403 }
404 
intervalChecker()405 IntervalCheck &AkonadiServer::intervalChecker()
406 {
407     return *mIntervalCheck;
408 }
409 
resourceManager()410 ResourceManager &AkonadiServer::resourceManager()
411 {
412     return *mResourceManager;
413 }
414 
notificationManager()415 NotificationManager *AkonadiServer::notificationManager()
416 {
417     return mNotificationManager.get();
418 }
419 
collectionStatistics()420 CollectionStatistics &AkonadiServer::collectionStatistics()
421 {
422     return *mCollectionStats;
423 }
424 
preprocessorManager()425 PreprocessorManager &AkonadiServer::preprocessorManager()
426 {
427     return *mPreprocessorManager;
428 }
429 
agentSearchManager()430 SearchTaskManager &AkonadiServer::agentSearchManager()
431 {
432     return *mAgentSearchManager;
433 }
434 
searchManager()435 SearchManager &AkonadiServer::searchManager()
436 {
437     return *mSearchManager;
438 }
439 
itemRetrievalManager()440 ItemRetrievalManager &AkonadiServer::itemRetrievalManager()
441 {
442     return *mItemRetrieval;
443 }
444 
tracer()445 Tracer &AkonadiServer::tracer()
446 {
447     return *mTracer;
448 }
449 
serverPath() const450 QString AkonadiServer::serverPath() const
451 {
452     return StandardDirs::saveDir("config");
453 }
454 
455 #include "akonadi.moc"
456