1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  * Copyright (C) 2013-2016 Canonical Ltd.
6  *
7  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  */
23 
24 #include <QCoreApplication>
25 #include <QDebug>
26 #include <QDir>
27 #include <QFile>
28 #include <QDBusConnection>
29 
30 #include "SignOn/misc.h"
31 
32 #include "backup.h"
33 #include "backup_adaptor.h"
34 #include "credentialsaccessmanager.h"
35 #include "signond-common.h"
36 
37 #define BACKUP_DIR_NAME() \
38     (QDir::separator() + QLatin1String("backup"))
39 
40 using namespace SignOn;
41 
42 namespace SignonDaemonNS {
43 
Backup(CredentialsAccessManager * cam,bool backupMode,QObject * parent)44 Backup::Backup(CredentialsAccessManager *cam, bool backupMode,
45                QObject *parent):
46     QObject(parent),
47     m_cam(cam),
48     m_backup(backupMode)
49 {
50     QDBusConnection sessionConnection = QDBusConnection::sessionBus();
51 
52     QDBusConnection::RegisterOptions registerSessionOptions =
53         QDBusConnection::ExportAdaptors;
54 
55     (void)new BackupclientAdaptor(this);
56 
57     if (!sessionConnection.registerObject(SIGNOND_DAEMON_OBJECTPATH
58                                           + QLatin1String("/Backup"),
59                                           this, registerSessionOptions)) {
60         TRACE() << "Object cannot be registered";
61 
62         qFatal("SignonDaemon requires to register backup object");
63     }
64 
65     if (!sessionConnection.registerService(SIGNOND_SERVICE +
66                                            QLatin1String(".Backup"))) {
67         QDBusError err = sessionConnection.lastError();
68         TRACE() << "Service cannot be registered: " <<
69             err.errorString(err.type());
70 
71         qFatal("SignonDaemon requires to register backup service");
72     }
73 }
74 
~Backup()75 Backup::~Backup()
76 {
77     QDBusConnection sessionConnection = QDBusConnection::sessionBus();
78     sessionConnection.unregisterObject(SIGNOND_DAEMON_OBJECTPATH
79                                        + QLatin1String("/Backup"));
80     sessionConnection.unregisterService(SIGNOND_SERVICE
81                                         + QLatin1String(".Backup"));
82 }
83 
eraseBackupDir() const84 void Backup::eraseBackupDir() const
85 {
86     const CAMConfiguration &config = m_cam->configuration();
87     QString backupRoot = config.m_storagePath + BACKUP_DIR_NAME();
88 
89     QDir target(backupRoot);
90     if (!target.exists()) return;
91 
92     QStringList targetEntries = target.entryList(QDir::Files);
93     foreach (QString entry, targetEntries) {
94         target.remove(entry);
95     }
96 
97     target.rmdir(backupRoot);
98 }
99 
copyToBackupDir(const QStringList & fileNames) const100 bool Backup::copyToBackupDir(const QStringList &fileNames) const
101 {
102     const CAMConfiguration &config = m_cam->configuration();
103     QString backupRoot = config.m_storagePath + BACKUP_DIR_NAME();
104 
105     QDir target(backupRoot);
106     if (!target.exists() && !target.mkpath(backupRoot)) {
107         qCritical() << "Cannot create target directory";
108         return false;
109     }
110 
111     setUserOwnership(backupRoot);
112 
113     /* Now copy the files to be backed up */
114     bool ok = true;
115     foreach (const QString &fileName, fileNames) {
116         /* Remove the target file, if it exists */
117         if (target.exists(fileName))
118             target.remove(fileName);
119 
120         /* Copy the source into the target directory */
121         QString source = config.m_storagePath + QDir::separator() + fileName;
122         if (!QFile::exists(source)) continue;
123 
124         QString destination = backupRoot + QDir::separator() + fileName;
125         ok = QFile::copy(source, destination);
126         if (!ok) {
127             BLAME() << "Copying" << source << "to" << destination << "failed";
128             break;
129         }
130 
131         setUserOwnership(destination);
132     }
133 
134     return ok;
135 }
136 
copyFromBackupDir(const QStringList & fileNames) const137 bool Backup::copyFromBackupDir(const QStringList &fileNames) const
138 {
139     const CAMConfiguration &config = m_cam->configuration();
140     QString backupRoot = config.m_storagePath + BACKUP_DIR_NAME();
141 
142     QDir sourceDir(backupRoot);
143     if (!sourceDir.exists()) {
144         TRACE() << "Backup directory does not exist!";
145     }
146 
147     if (!sourceDir.exists(config.m_dbName)) {
148         TRACE() << "Backup does not contain DB:" << config.m_dbName;
149     }
150 
151     /* Now restore the files from the backup */
152     bool ok = true;
153     QDir target(config.m_storagePath);
154     QStringList movedFiles, copiedFiles;
155     foreach (const QString &fileName, fileNames) {
156         /* Rename the target file, if it exists */
157         if (target.exists(fileName)) {
158             if (target.rename(fileName, fileName + QLatin1String(".bak")))
159                 movedFiles += fileName;
160         }
161 
162         /* Copy the source into the target directory */
163         QString source = backupRoot + QDir::separator() + fileName;
164         if (!QFile::exists(source)) {
165             TRACE() << "Ignoring file not present in backup:" << source;
166             continue;
167         }
168 
169         QString destination =
170             config.m_storagePath + QDir::separator() + fileName;
171 
172         ok = QFile::copy(source, destination);
173         if (ok) {
174             copiedFiles << fileName;
175         } else {
176             qWarning() << "Copy failed for:" << source;
177             break;
178         }
179     }
180 
181     if (!ok) {
182         qWarning() << "Restore failed, recovering previous DB";
183 
184         foreach (const QString &fileName, copiedFiles) {
185             target.remove(fileName);
186         }
187 
188         foreach (const QString &fileName, movedFiles) {
189             if (!target.rename(fileName + QLatin1String(".bak"), fileName)) {
190                 qCritical() << "Could not recover:" << fileName;
191             }
192         }
193     } else {
194         /* delete ".bak" files */
195         foreach (const QString &fileName, movedFiles) {
196             target.remove(fileName + QLatin1String(".bak"));
197         }
198 
199     }
200     return ok;
201 }
202 
createStorageFileTree(const QStringList & backupFiles) const203 bool Backup::createStorageFileTree(const QStringList &backupFiles) const
204 {
205     const CAMConfiguration &config = m_cam->configuration();
206     QDir storageDir(config.m_storagePath);
207 
208     if (!storageDir.exists()) {
209         if (!storageDir.mkpath(config.m_storagePath)) {
210             qCritical() << "Could not create storage dir for backup.";
211             return false;
212         }
213     }
214 
215     foreach (const QString &fileName, backupFiles) {
216         if (storageDir.exists(fileName)) continue;
217 
218         QString filePath = storageDir.path() + QDir::separator() + fileName;
219         QFile file(filePath);
220         if (!file.open(QIODevice::WriteOnly)) {
221             qCritical() << "Failed to create empty file for backup:" << filePath;
222             return false;
223         } else {
224             file.close();
225         }
226     }
227 
228     return true;
229 }
230 
backupStarts()231 uchar Backup::backupStarts()
232 {
233     TRACE() << "backup";
234     if (!m_backup && m_cam->credentialsSystemOpened())
235     {
236         m_cam->closeCredentialsSystem();
237         if (m_cam->credentialsSystemOpened())
238         {
239             qCritical() << "Cannot close credentials database";
240             return 2;
241         }
242     }
243 
244     const CAMConfiguration &config = m_cam->configuration();
245 
246     /* do backup copy: prepare the list of files to be backed up */
247     QStringList backupFiles;
248     backupFiles << config.m_dbName;
249     backupFiles << m_cam->backupFiles();
250 
251     /* make sure that all the backup files and storage directory exist:
252        create storage dir and empty files if not so, as backup/restore
253        operations must be consistent */
254     if (!createStorageFileTree(backupFiles)) {
255         qCritical() << "Cannot create backup file tree.";
256         return 2;
257     }
258 
259     /* perform the copy */
260     eraseBackupDir();
261     if (!copyToBackupDir(backupFiles)) {
262         qCritical() << "Cannot copy database";
263         if (!m_backup)
264             m_cam->openCredentialsSystem();
265         return 2;
266     }
267 
268     if (!m_backup)
269     {
270         //mount file system back
271         if (!m_cam->openCredentialsSystem()) {
272             qCritical() << "Cannot reopen database";
273         }
274     }
275     return 0;
276 }
277 
backupFinished()278 uchar Backup::backupFinished()
279 {
280     TRACE() << "close";
281 
282     eraseBackupDir();
283 
284     if (m_backup)
285     {
286         //close daemon
287         TRACE() << "close daemon";
288         QCoreApplication::quit();
289     }
290 
291     return 0;
292  }
293 
294 /*
295  * Does nothing but start-on-demand
296  * */
restoreStarts()297 uchar Backup::restoreStarts()
298 {
299     TRACE();
300     return 0;
301 }
302 
restoreFinished()303 uchar Backup::restoreFinished()
304 {
305     TRACE() << "restore";
306     //restore requested
307     if (m_cam->credentialsSystemOpened())
308     {
309         //umount file system
310         if (!m_cam->closeCredentialsSystem())
311         {
312             qCritical() << "database cannot be closed";
313             return 2;
314         }
315     }
316 
317     const CAMConfiguration &config = m_cam->configuration();
318 
319     QStringList backupFiles;
320     backupFiles << config.m_dbName;
321     backupFiles << m_cam->backupFiles();
322 
323     /* perform the copy */
324     if (!copyFromBackupDir(backupFiles)) {
325         qCritical() << "Cannot copy database";
326         m_cam->openCredentialsSystem();
327         return 2;
328     }
329 
330     eraseBackupDir();
331 
332     //TODO check database integrity
333     if (!m_backup)
334     {
335         //mount file system back
336          if (!m_cam->openCredentialsSystem())
337              return 2;
338     }
339 
340     return 0;
341 }
342 
343 } //namespace SignonDaemonNS
344