]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser_launcher/common.py
Add 'my' translation
[torbrowser-launcher.git] / torbrowser_launcher / common.py
1 """
2 Tor Browser Launcher
3 https://github.com/micahflee/torbrowser-launcher/
4
5 Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
6
7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use,
11 copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 OTHER DEALINGS IN THE SOFTWARE.
27 """
28
29 import os
30 import sys
31 import platform
32 import subprocess
33 import locale
34 import pickle
35 import json
36 import re
37 import gettext
38 import gpg
39
40 SHARE = os.getenv("TBL_SHARE", sys.prefix + "/share") + "/torbrowser-launcher"
41
42 gettext.install("torbrowser-launcher")
43
44 # We're looking for output which:
45 #
46 #  1. The first portion must be `[GNUPG:] IMPORT_OK`
47 #  2. The second must be an integer between [0, 15], inclusive
48 #  3. The third must be an uppercased hex-encoded 160-bit fingerprint
49 gnupg_import_ok_pattern = re.compile(
50     b"(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})"
51 )
52
53
54 class Common(object):
55     def __init__(self, tbl_version):
56         self.tbl_version = tbl_version
57
58         # initialize the app
59         self.default_mirror = "https://dist.torproject.org/"
60         self.discover_arch_lang()
61         self.build_paths()
62         for d in self.paths["dirs"]:
63             self.mkdir(self.paths["dirs"][d])
64         self.load_mirrors()
65         self.load_settings()
66         self.mkdir(self.paths["download_dir"])
67         self.mkdir(self.paths["tbb"]["dir"])
68         self.init_gnupg()
69
70     # discover the architecture and language
71     def discover_arch_lang(self):
72         # figure out the architecture
73         self.architecture = "x86_64" if "64" in platform.architecture()[0] else "i686"
74
75         # figure out the language
76         available_languages = [
77             "ar",
78             "ca",
79             "cs",
80             "da",
81             "de",
82             "el",
83             "en-US",
84             "es-AR",
85             "es-ES",
86             "fa",
87             "fr",
88             "ga-IE",
89             "he",
90             "hu",
91             "id",
92             "is",
93             "it",
94             "ja",
95             "ka",
96             "ko",
97             "lt",
98             "mk",
99             "ms",
100             "my",
101             "nb-NO",
102             "nl",
103             "pl",
104             "pt-BR",
105             "ro",
106             "ru",
107             "sv-SE",
108             "th",
109             "tr",
110             "vi",
111             "zh-CN",
112             "zh-TW",
113         ]
114         default_locale = locale.getlocale()[0]
115         if default_locale is None:
116             self.language = "en-US"
117         else:
118             self.language = default_locale.replace("_", "-")
119             if self.language not in available_languages:
120                 self.language = self.language.split("-")[0]
121                 if self.language not in available_languages:
122                     for l in available_languages:
123                         if l[0:2] == self.language:
124                             self.language = l
125             # if language isn't available, default to english
126             if self.language not in available_languages:
127                 self.language = "en-US"
128
129     # get value of environment variable, if it is not set return the default value
130     @staticmethod
131     def get_env(var_name, default_value):
132         value = os.getenv(var_name)
133         if not value:
134             value = default_value
135         return value
136
137     # build all relevant paths
138     def build_paths(self, tbb_version=None):
139         homedir = os.getenv("HOME")
140         if not homedir:
141             homedir = "/tmp/.torbrowser-" + os.getenv("USER")
142             if not os.path.exists(homedir):
143                 try:
144                     os.mkdir(homedir, 0o700)
145                 except:
146                     self.set_gui(
147                         "error", _("Error creating {0}").format(homedir), [], False
148                     )
149         if not os.access(homedir, os.W_OK):
150             self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
151
152         tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
153         tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
154         tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
155         old_tbb_data = '{0}/.torbrowser'.format(homedir)
156
157         if tbb_version:
158             # tarball filename
159             if self.architecture == "x86_64":
160                 arch = "linux64"
161             else:
162                 arch = "linux32"
163
164             if hasattr(self, "settings") and self.settings["force_en-US"]:
165                 language = "en-US"
166             else:
167                 language = self.language
168             tarball_filename = (
169                 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
170             )
171
172             # tarball
173             self.paths["tarball_url"] = (
174                 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
175             )
176             self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
177             self.paths["tarball_filename"] = tarball_filename
178
179             # sig
180             self.paths["sig_url"] = (
181                 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
182             )
183             self.paths["sig_file"] = (
184                 tbb_cache + "/download/" + tarball_filename + ".asc"
185             )
186             self.paths["sig_filename"] = tarball_filename + ".asc"
187         else:
188             self.paths = {
189                 "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,},
190                 "old_data_dir": old_tbb_data,
191                 "tbl_bin": sys.argv[0],
192                 "icon_file": os.path.join(
193                     os.path.dirname(SHARE), "pixmaps/torbrowser.png"
194                 ),
195                 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
196                 "signing_keys": {
197                     "tor_browser_developers": os.path.join(
198                         SHARE, "tor-browser-developers.asc"
199                     )
200                 },
201                 "mirrors_txt": [
202                     os.path.join(SHARE, "mirrors.txt"),
203                     tbb_config + "/mirrors.txt",
204                 ],
205                 "download_dir": tbb_cache + "/download",
206                 "gnupg_homedir": tbb_local + "/gnupg_homedir",
207                 "settings_file": tbb_config + "/settings.json",
208                 "settings_file_pickle": tbb_config + "/settings",
209                 "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
210                 "version_check_file": tbb_cache + "/download/release.xml",
211                 "tbb": {
212                     "changelog": tbb_local
213                     + "/tbb/"
214                     + self.architecture
215                     + "/tor-browser_"
216                     + self.language
217                     + "/Browser/TorBrowser/Docs/ChangeLog.txt",
218                     "dir": tbb_local + "/tbb/" + self.architecture,
219                     "dir_tbb": tbb_local
220                     + "/tbb/"
221                     + self.architecture
222                     + "/tor-browser_"
223                     + self.language,
224                     "start": tbb_local
225                     + "/tbb/"
226                     + self.architecture
227                     + "/tor-browser_"
228                     + self.language
229                     + "/start-tor-browser.desktop",
230                 },
231             }
232
233         # Add the expected fingerprint for imported keys:
234         self.fingerprints = {
235             "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
236         }
237
238     # create a directory
239     @staticmethod
240     def mkdir(path):
241         try:
242             if not os.path.exists(path):
243                 os.makedirs(path, 0o700)
244                 return True
245         except:
246             print(_("Cannot create directory {0}").format(path))
247             return False
248         if not os.access(path, os.W_OK):
249             print(_("{0} is not writable").format(path))
250             return False
251         return True
252
253     # if gnupg_homedir isn't set up, set it up
254     def init_gnupg(self):
255         if not os.path.exists(self.paths["gnupg_homedir"]):
256             print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
257             self.mkdir(self.paths["gnupg_homedir"])
258         self.import_keys()
259
260     def refresh_keyring(self, fingerprint=None):
261         if fingerprint is not None:
262             print("Refreshing local keyring... Missing key: " + fingerprint)
263         else:
264             print("Refreshing local keyring...")
265
266         # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
267         p = subprocess.Popen(
268             [
269                 "gpg2",
270                 "--status-fd",
271                 "2",
272                 "--homedir",
273                 self.paths["gnupg_homedir"],
274                 "--auto-key-locate",
275                 "nodefault,wkd",
276                 "--locate-keys",
277                 "torbrowser@torproject.org",
278             ],
279             stderr=subprocess.PIPE,
280         )
281         p.wait()
282
283         for output in p.stderr.readlines():
284             match = gnupg_import_ok_pattern.match(output)
285             if match and match.group(2) == "IMPORT_OK":
286                 fingerprint = str(match.group(4))
287                 if match.group(3) == "0":
288                     print("Keyring refreshed successfully...")
289                     print("  No key updates for key: " + fingerprint)
290                 elif match.group(3) == "4":
291                     print("Keyring refreshed successfully...")
292                     print("  New signatures for key: " + fingerprint)
293                 else:
294                     print("Keyring refreshed successfully...")
295
296     def import_key_and_check_status(self, key):
297         """Import a GnuPG key and check that the operation was successful.
298         :param str key: A string specifying the key's filepath from
299             ``Common.paths``
300         :rtype: bool
301         :returns: ``True`` if the key is now within the keyring (or was
302             previously and hasn't changed). ``False`` otherwise.
303         """
304         with gpg.Context() as c:
305             c.set_engine_info(
306                 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
307             )
308
309             impkey = self.paths["signing_keys"][key]
310             try:
311                 c.op_import(gpg.Data(file=impkey))
312             except:
313                 return False
314             else:
315                 result = c.op_import_result()
316                 if result and self.fingerprints[key] in result.imports[0].fpr:
317                     return True
318                 else:
319                     return False
320
321     # import gpg keys
322     def import_keys(self):
323         """Import all GnuPG keys.
324         :rtype: bool
325         :returns: ``True`` if all keys were successfully imported; ``False``
326             otherwise.
327         """
328         keys = [
329             "tor_browser_developers",
330         ]
331         all_imports_succeeded = True
332
333         for key in keys:
334             imported = self.import_key_and_check_status(key)
335             if not imported:
336                 print(
337                     _(
338                         "Could not import key with fingerprint: %s."
339                         % self.fingerprints[key]
340                     )
341                 )
342                 all_imports_succeeded = False
343
344         if not all_imports_succeeded:
345             print(_("Not all keys were imported successfully!"))
346
347         return all_imports_succeeded
348
349     # load mirrors
350     def load_mirrors(self):
351         self.mirrors = []
352         for srcfile in self.paths["mirrors_txt"]:
353             if not os.path.exists(srcfile):
354                 continue
355             for mirror in open(srcfile, "r").readlines():
356                 if mirror.strip() not in self.mirrors:
357                     self.mirrors.append(mirror.strip())
358
359     # load settings
360     def load_settings(self):
361         default_settings = {
362             "tbl_version": self.tbl_version,
363             "installed": False,
364             "download_over_tor": False,
365             "tor_socks_address": "127.0.0.1:9050",
366             "mirror": self.default_mirror,
367             "force_en-US": False,
368         }
369
370         if os.path.isfile(self.paths["settings_file"]):
371             settings = json.load(open(self.paths["settings_file"]))
372             resave = False
373
374             # detect installed
375             settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
376
377             # make sure settings file is up-to-date
378             for setting in default_settings:
379                 if setting not in settings:
380                     settings[setting] = default_settings[setting]
381                     resave = True
382
383             # make sure tor_socks_address doesn't start with 'tcp:'
384             if settings["tor_socks_address"].startswith("tcp:"):
385                 settings["tor_socks_address"] = settings["tor_socks_address"][4:]
386                 resave = True
387
388             # make sure the version is current
389             if settings["tbl_version"] != self.tbl_version:
390                 settings["tbl_version"] = self.tbl_version
391                 resave = True
392
393             self.settings = settings
394             if resave:
395                 self.save_settings()
396
397         # if settings file is still using old pickle format, convert to json
398         elif os.path.isfile(self.paths["settings_file_pickle"]):
399             self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
400             self.save_settings()
401             os.remove(self.paths["settings_file_pickle"])
402             self.load_settings()
403
404         else:
405             self.settings = default_settings
406             self.save_settings()
407
408     # save settings
409     def save_settings(self):
410         json.dump(self.settings, open(self.paths["settings_file"], "w"))
411         return True