1 /*
2  * Copyright 2014 Canonical Ltd.
3  * Authors:
4  *      Roberto Mier
5  *      Tiago Herrmann
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "settings.h"
22 #include "util/utils.h"
23 #include "util/constants.h"
24 #include "dc.h"
25 #include "telegram.h"
26 
27 #include <QDir>
28 #include <QSettings>
29 #include <QDebug>
30 #include <fcntl.h>
31 #if !defined(Q_OS_WIN)
32 #include <unistd.h>
33 #endif
34 
35 #include <openssl/bn.h>
36 
37 Q_LOGGING_CATEGORY(TG_CORE_SETTINGS, "tg.core.settings")
38 
telegram_settings_auth_path(const QString & configPath,const QString & phone)39 QString telegram_settings_auth_path(const QString &configPath, const QString &phone)
40 {
41     const QString phoneNumber = Utils::parsePhoneNumberDigits(phone);
42     const QString pathDir = configPath + "/" + phoneNumber;
43 
44     QDir().mkpath(pathDir);
45     return pathDir + '/' + AUTH_KEY_FILE;
46 }
47 
telegram_settings_read_fnc(const QString & configPath,const QString & phone,QVariantMap & map)48 bool telegram_settings_read_fnc(const QString &configPath, const QString &phone, QVariantMap &map)
49 {
50     const QString configFilename = telegram_settings_auth_path(configPath, phone);
51 
52     QSettings settings(configFilename, QSettings::IniFormat);
53     const QStringList &keys = settings.allKeys();
54     foreach(const QString &k, keys)
55         map[k] = settings.value(k);
56 
57     return true;
58 }
59 
telegram_settings_write_fnc(const QString & configPath,const QString & phone,const QVariantMap & map)60 bool telegram_settings_write_fnc(const QString &configPath, const QString &phone, const QVariantMap &map)
61 {
62     const QString configFilename = telegram_settings_auth_path(configPath, phone);
63 
64     QSettings settings(configFilename, QSettings::IniFormat);
65     QMapIterator<QString,QVariant> i(map);
66     while(i.hasNext())
67     {
68         i.next();
69         settings.setValue(i.key(), i.value());
70     }
71 
72     return true;
73 }
74 
75 Settings::ReadFunc _telegram_settings_read_fnc = telegram_settings_read_fnc;
76 Settings::WriteFunc _telegram_settings_write_fnc = telegram_settings_write_fnc;
77 
Settings()78 Settings::Settings() :
79     m_defaultHostPort(0),
80     m_defaultHostDcId(0),
81     m_appId(0),
82     m_workingDcConfigAvailabe(false),
83     m_pubKey(0),
84     m_phoneNumber(""),
85     m_baseConfigDirectory("") {
86 }
87 
~Settings()88 Settings::~Settings() {
89     if (m_pubKey) {
90         delete m_pubKey;
91     }
92 }
93 
setDefaultHostAddress(const QString & host)94 void Settings::setDefaultHostAddress(const QString &host)
95 {
96     m_defaultHostAddress = host;
97 }
98 
setDefaultHostPort(qint16 port)99 void Settings::setDefaultHostPort(qint16 port)
100 {
101     m_defaultHostPort = port;
102 }
103 
setDefaultHostDcId(qint16 dcId)104 void Settings::setDefaultHostDcId(qint16 dcId)
105 {
106     m_defaultHostDcId = dcId;
107 }
108 
setAppId(qint32 appId)109 void Settings::setAppId(qint32 appId)
110 {
111     m_appId = appId;
112 }
113 
setAppHash(const QString & appHash)114 void Settings::setAppHash(const QString &appHash)
115 {
116     m_appHash = appHash;
117 }
118 
defaultHostAddress()119 QString Settings::defaultHostAddress()
120 {
121     return m_defaultHostAddress;
122 }
123 
defaultHostPort()124 qint16 Settings::defaultHostPort()
125 {
126     return m_defaultHostPort;
127 }
128 
defaultHostDcId()129 qint16 Settings::defaultHostDcId()
130 {
131     return m_defaultHostDcId;
132 }
133 
appId()134 qint32 Settings::appId()
135 {
136     return m_appId;
137 }
138 
appHash()139 QString Settings::appHash()
140 {
141     return m_appHash;
142 }
143 
loadSettings(const QString & phoneNumber,const QString & baseConfigDirectory,const QString & publicKeyFile)144 bool Settings::loadSettings(const QString &phoneNumber, const QString &baseConfigDirectory, const QString &publicKeyFile) {
145 
146     m_phoneNumber = Utils::parsePhoneNumberDigits(phoneNumber);
147 
148     if (m_baseConfigDirectory.isEmpty()) {
149         m_baseConfigDirectory = baseConfigDirectory;
150     }
151 
152     QString configDirectory = m_baseConfigDirectory + "/" + m_phoneNumber;
153     QString configPath = QString(configDirectory).replace("~",QDir::homePath());
154 
155     if (!m_pubKey) {
156         m_pubKey = Utils::rsaLoadPublicKey(publicKeyFile);
157         if(!m_pubKey)
158             return false;
159 
160         m_pkFingerprint = Utils::computeRSAFingerprint(m_pubKey);
161         qCDebug(TG_CORE_SETTINGS) << "loaded Telegram public key from file:" << publicKeyFile;
162     }
163 
164     qCDebug(TG_CORE_SETTINGS) << "loading configuration from path:" << configPath;
165     QDir configDir = QDir(configPath);
166     if (!configDir.exists()) {
167         if (QDir::root().mkpath(configPath)) {
168             qCDebug(TG_CORE_SETTINGS) << configPath << "config directory created";
169         } else {
170             qCWarning(TG_CORE_SETTINGS) << "error creating config directory: " << configPath;
171             return false;
172         }
173     }
174 
175     //check if config file is there and it's readable
176     QString configFilename = configPath + '/' + CONFIG_FILE;
177     QFile configFile(configFilename);
178     if (!configFile.exists()) {
179         // config file missing, touch it
180         if (configFile.open(QIODevice::WriteOnly)) {
181             QTextStream out(&configFile);
182             out << DEFAULT_CONFIG_CONTENTS;
183             configFile.close();
184         } else {
185             qCWarning(TG_CORE_SETTINGS) << "Impossible to open file " << configFilename << " for writting default configuration";
186             return false;
187         }
188     }
189 
190     //read settings
191     qCDebug(TG_CORE_SETTINGS) << "loading settings file" << configFilename;
192     QSettings settings(configFilename, QSettings::IniFormat);
193     m_testMode = settings.value(ST_TEST_MODE, false).toBool();
194     m_managedDownloads = settings.value(ST_MANAGED_DOWNLOADS, false).toBool();
195     m_langCode = settings.value(ST_LANG_CODE, "en").toString();
196     mResendQueries = settings.value(ST_RESEND_QUERIES, false).toBool();
197     m_authFilename = settings.value(ST_AUTH_FILE, configPath + '/' + AUTH_KEY_FILE).toString();
198     m_secretChatFilename = settings.value(ST_SECRET, configPath + '/' + SECRET_CHAT_FILE).toString();
199     m_stateFilename = settings.value(ST_STATE_FILE, configPath + '/' + STATE_FILE).toString();
200 
201     // log readed data
202     qCDebug(TG_CORE_SETTINGS) << "testMode:" << m_testMode;
203     qCDebug(TG_CORE_SETTINGS) << "managedDownloads:" << m_managedDownloads;
204     qCDebug(TG_CORE_SETTINGS) << "lang:" << m_langCode;
205     qCDebug(TG_CORE_SETTINGS) << "authFile:" << m_authFilename;
206     qCDebug(TG_CORE_SETTINGS) << "secretChatFile:" << m_secretChatFilename;
207     qCDebug(TG_CORE_SETTINGS) << "stateFile:" << m_stateFilename;
208 
209     readAuthFile();
210     readSecretFile();
211 
212     return true;
213 }
214 
writeAuthFile()215 void Settings::writeAuthFile() {
216     QVariantMap map;
217     QString pre = testMode() ? ST_TEST : ST_PRODUCTION;
218     pre += "/";
219 
220     map[pre + ST_WORKING_DC_NUM] = m_workingDcNum;
221     map[pre + ST_OUR_ID] = m_ourId;
222 
223     pre += ST_DCS_ARRAY;
224     pre += "/";
225 
226     map[pre + "size"] = m_dcsList.length();
227     for (qint32 i = 0; i < m_dcsList.length(); i++) {
228         QString ar = pre + QString::number(i) + "/";
229 
230         map[ar + ST_DC_NUM] = m_dcsList[i]->id();
231         map[ar + ST_HOST] = m_dcsList[i]->host();
232         map[ar + ST_PORT] = m_dcsList[i]->port();
233         map[ar + ST_DC_STATE] = m_dcsList[i]->state();
234 
235         if (m_dcsList[i]->authKeyId()) {
236             map[ar + ST_AUTH_KEY_ID] = m_dcsList[i]->authKeyId();
237             QByteArray baToSave(m_dcsList[i]->authKey(), SHARED_KEY_LENGTH);
238             map[ar + ST_AUTH_KEY] = baToSave.toBase64();
239         }
240         map[ar + ST_SERVER_SALT] = m_dcsList[i]->serverSalt();
241         map[ar + ST_EXPIRES] = m_dcsList[i]->expires();
242     }
243 
244     if(!_telegram_settings_write_fnc(m_baseConfigDirectory, m_phoneNumber, map))
245         telegram_settings_write_fnc(m_baseConfigDirectory, m_phoneNumber, map);
246 }
247 
readAuthFile()248 void Settings::readAuthFile() {
249     QVariantMap map;
250     if(!_telegram_settings_read_fnc(m_baseConfigDirectory, m_phoneNumber, map))
251         telegram_settings_read_fnc(m_baseConfigDirectory, m_phoneNumber, map);
252 
253     QString pre = testMode() ? ST_TEST : ST_PRODUCTION;
254     pre += "/";
255 
256     qint32 defaultDcId = m_testMode ? TEST_DEFAULT_DC_ID : Settings::defaultHostDcId();
257 
258     m_workingDcNum = map.value(pre+ST_WORKING_DC_NUM, defaultDcId).toInt();
259     m_ourId = map.value(pre+ST_OUR_ID).toInt();
260     m_workingDcConfigAvailabe = map.contains(pre+ST_WORKING_DC_NUM);
261     qCDebug(TG_CORE_SETTINGS) << "workingDcNum:" << m_workingDcNum;
262     qCDebug(TG_CORE_SETTINGS) << "ourId:" << m_ourId;
263 
264     pre += ST_DCS_ARRAY;
265     pre += "/";
266 
267     qint32 n = map.value(pre + "size").toInt();
268     for (qint32 i = 0; i < n; i++) {
269         QString ar = pre + QString::number(i) + "/";
270 
271         qint32 dcNum = map.value(ar+ST_DC_NUM).toInt();
272         DC* dc = new DC(dcNum);
273         dc->setHost(map.value(ar+ST_HOST).toString());
274         dc->setPort(map.value(ar+ST_PORT, 0).toInt());
275         dc->setState((DC::DcState)map.value(ar+ST_DC_STATE, DC::init).toInt());
276         dc->setAuthKeyId(map.value(ar+ST_AUTH_KEY_ID, 0).toLongLong());
277         if (dc->state() >= DC::authKeyCreated) {
278             QByteArray readedBa = QByteArray::fromBase64(map.value(ar+ST_AUTH_KEY).toByteArray());
279             memcpy(dc->authKey(), readedBa.data(), SHARED_KEY_LENGTH);
280         }
281         dc->setServerSalt(map.value(ar+ST_SERVER_SALT, 0).toLongLong());
282         dc->setExpires(map.value(ar+ST_EXPIRES).toInt());
283 
284         qCDebug(TG_CORE_SETTINGS) << "DC | id:" << dc->id() << ", state:" << dc->state() <<
285                     ", host:" << dc->host() << ", port:" << dc->port() <<
286                     ", expires:" << dc->expires() << ", authKeyId:" << dc->authKeyId() <<
287                     ", serverSalt:" << dc->serverSalt();
288 
289         m_dcsList.insert(dcNum, dc);
290     }
291 }
292 
removeAuthFile()293 bool Settings::removeAuthFile() {
294     QFile authFile(m_authFilename);
295     if (authFile.exists()) {
296         if (authFile.remove()) {
297             qCDebug(TG_CORE_SETTINGS) << "Auth file has been reset";
298             return true;
299         } else {
300             qCDebug(TG_CORE_SETTINGS) << "An error has happened when trying to remove auth file";
301         }
302     }
303     return false;
304 }
305 
setAuthConfigMethods(Settings::ReadFunc readFunc,Settings::WriteFunc writeFunc)306 void Settings::setAuthConfigMethods(Settings::ReadFunc readFunc, Settings::WriteFunc writeFunc)
307 {
308     _telegram_settings_read_fnc = readFunc;
309     _telegram_settings_write_fnc = writeFunc;
310 }
311 
clearAuth(const QString & configPath,const QString & phone)312 void Settings::clearAuth(const QString &configPath, const QString &phone)
313 {
314     _telegram_settings_write_fnc(configPath, phone, QVariantMap());
315 }
316 
readSecretFile()317 void Settings::readSecretFile() {
318     QSettings settings(m_secretChatFilename, QSettings::IniFormat);
319     mVersion = settings.value(ST_VERSION, 0).toInt();
320     mG = settings.value(ST_G, 0).toInt();
321     mP = QByteArray::fromBase64(settings.value(ST_P).toByteArray());
322 
323     qCDebug(TG_CORE_SETTINGS) << "secret chats dh version:" << mVersion;
324     qCDebug(TG_CORE_SETTINGS) << "secret chats g:" << mG;
325     qCDebug(TG_CORE_SETTINGS) << "secret chats p:" << mP.toBase64();
326 
327     mSecretChats.clear();
328     qint32 n = settings.beginReadArray(ST_SECRET_CHATS_ARRAY);
329     for (qint32 i = 0; i < n; i++) {
330         settings.setArrayIndex(i);
331         SecretChat *secretChat = new SecretChat(this);
332         secretChat->setState(SecretChat::Accepted); // only accepted chats are saved.
333         secretChat->setChatId(settings.value(ST_CHAT_ID, 0).toInt());
334         secretChat->setAccessHash(settings.value(ST_ACCESS_HASH, 0).toLongLong());
335         secretChat->setAdminId(settings.value(ST_ADMIN_ID, 0).toInt());
336         secretChat->setParticipantId(settings.value(ST_PARTICIPANT_ID, 0).toInt());
337         secretChat->setDate(settings.value(ST_DATE, 0).toInt());
338         secretChat->setState((SecretChat::State)settings.value(ST_STATE, 0).toInt());
339 
340         QByteArray base64Sk = settings.value(ST_SHARED_KEY).toByteArray();
341         if (!base64Sk.isEmpty()) {
342             QByteArray parsedSk = QByteArray::fromBase64(base64Sk);
343             memcpy(secretChat->sharedKey(), parsedSk.data(), SHARED_KEY_LENGTH);
344         }
345 
346         QByteArray base64Mk = settings.value(ST_MY_KEY).toByteArray();
347         if (!base64Mk.isEmpty()) {
348             QByteArray parsedMk = QByteArray::fromBase64(base64Mk);
349             BIGNUM *myKey = Utils::bytesToBignum(parsedMk);
350             secretChat->setMyKey(myKey);
351         }
352 
353         secretChat->setLayer(settings.value(ST_LAYER, 0).toInt());
354         secretChat->setInSeqNo(settings.value(ST_IN_SEQ_NO, 0).toInt());
355         secretChat->setOutSeqNo(settings.value(ST_OUT_SEQ_NO, 0).toInt());
356         mSecretChats.append(secretChat);
357 
358         qCDebug(TG_CORE_SETTINGS) << "SecretChat:\nid:" << secretChat->chatId()
359                                   << "\nadminId:" << secretChat->adminId()
360                                   << "\nparticipantId:" << secretChat->participantId()
361                                   << "\ndate:" << secretChat->date()
362                                   << "\nsharedKey (keyFingerprint):" << secretChat->keyFingerprint()
363                                   << "\nlayer:" << secretChat->layer()
364                                   << "\ninSeqNo:" << secretChat->inSeqNo()
365                                   << "\noutSeqNo:" << secretChat->outSeqNo();
366     }
367     settings.endArray();
368 }
369 
370 
writeSecretFile()371 void Settings::writeSecretFile() {
372     QSettings settings(m_secretChatFilename, QSettings::IniFormat);
373     settings.clear();
374     settings.setValue(ST_VERSION, mVersion);
375     settings.setValue(ST_G, mG);
376     settings.setValue(ST_P, mP.toBase64());
377     settings.beginWriteArray(ST_SECRET_CHATS_ARRAY);
378     for (qint32 i = 0; i < mSecretChats.length(); i++) {
379         SecretChat *secretChat = mSecretChats[i];
380         settings.setArrayIndex(i);
381         settings.setValue(ST_CHAT_ID, secretChat->chatId());
382         settings.setValue(ST_ACCESS_HASH, secretChat->accessHash());
383         settings.setValue(ST_ADMIN_ID, secretChat->adminId());
384         settings.setValue(ST_PARTICIPANT_ID, secretChat->participantId());
385         settings.setValue(ST_DATE, secretChat->date());
386         settings.setValue(ST_STATE, secretChat->state());
387 
388         BIGNUM *myKey = secretChat->myKey();
389         if (myKey) {
390             QByteArray myKeyToSave = Utils::bignumToBytes(myKey);
391             settings.setValue(ST_MY_KEY, myKeyToSave.toBase64());
392         }
393 
394         if (secretChat->sharedKey()) {
395             QByteArray sharedKeyToSave((char *)secretChat->sharedKey(), SHARED_KEY_LENGTH);
396             settings.setValue(ST_SHARED_KEY, sharedKeyToSave.toBase64());
397         }
398 
399         settings.setValue(ST_LAYER, secretChat->layer());
400         settings.setValue(ST_IN_SEQ_NO, secretChat->inSeqNo());
401         settings.setValue(ST_OUT_SEQ_NO, secretChat->outSeqNo());
402     }
403     settings.endArray();
404 }
405 
406