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