3 https://github.com/micahflee/torbrowser-launcher/
5 Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
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
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
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.
40 SHARE = os.getenv("TBL_SHARE", sys.prefix + "/share") + "/torbrowser-launcher"
42 gettext.install("torbrowser-launcher")
44 # We're looking for output which:
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})"
55 def __init__(self, tbl_version):
56 self.tbl_version = tbl_version
59 self.default_mirror = "https://dist.torproject.org/"
60 self.discover_arch_lang()
62 for d in self.paths["dirs"]:
63 self.mkdir(self.paths["dirs"][d])
66 self.mkdir(self.paths["download_dir"])
67 self.mkdir(self.paths["tbb"]["dir"])
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"
75 # figure out the language
76 available_languages = [
103 default_locale = locale.getlocale()[0]
104 if default_locale is None:
105 self.language = "en-US"
107 self.language = default_locale.replace("_", "-")
108 if self.language not in available_languages:
109 self.language = self.language.split("-")[0]
110 if self.language not in available_languages:
111 for l in available_languages:
112 if l[0:2] == self.language:
114 # if language isn't available, default to english
115 if self.language not in available_languages:
116 self.language = "en-US"
118 # get value of environment variable, if it is not set return the default value
120 def get_env(var_name, default_value):
121 value = os.getenv(var_name)
123 value = default_value
126 # build all relevant paths
127 def build_paths(self, tbb_version=None):
128 homedir = os.getenv("HOME")
130 homedir = "/tmp/.torbrowser-" + os.getenv("USER")
131 if not os.path.exists(homedir):
133 os.mkdir(homedir, 0o700)
136 "error", _("Error creating {0}").format(homedir), [], False
138 if not os.access(homedir, os.W_OK):
139 self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
141 tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
142 tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
143 tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
144 old_tbb_data = '{0}/.torbrowser'.format(homedir)
148 if self.architecture == "x86_64":
153 if hasattr(self, "settings") and self.settings["force_en-US"]:
156 language = self.language
158 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
162 self.paths["tarball_url"] = (
163 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
165 self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
166 self.paths["tarball_filename"] = tarball_filename
169 self.paths["sig_url"] = (
170 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
172 self.paths["sig_file"] = (
173 tbb_cache + "/download/" + tarball_filename + ".asc"
175 self.paths["sig_filename"] = tarball_filename + ".asc"
178 "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,},
179 "old_data_dir": old_tbb_data,
180 "tbl_bin": sys.argv[0],
181 "icon_file": os.path.join(
182 os.path.dirname(SHARE), "pixmaps/torbrowser.png"
184 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
186 "tor_browser_developers": os.path.join(
187 SHARE, "tor-browser-developers.asc"
191 os.path.join(SHARE, "mirrors.txt"),
192 tbb_config + "/mirrors.txt",
194 "download_dir": tbb_cache + "/download",
195 "gnupg_homedir": tbb_local + "/gnupg_homedir",
196 "settings_file": tbb_config + "/settings.json",
197 "settings_file_pickle": tbb_config + "/settings",
198 "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
199 "version_check_file": tbb_cache + "/download/release.xml",
201 "changelog": tbb_local
206 + "/Browser/TorBrowser/Docs/ChangeLog.txt",
207 "dir": tbb_local + "/tbb/" + self.architecture,
218 + "/start-tor-browser.desktop",
222 # Add the expected fingerprint for imported keys:
223 self.fingerprints = {
224 "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
231 if not os.path.exists(path):
232 os.makedirs(path, 0o700)
235 print(_("Cannot create directory {0}").format(path))
237 if not os.access(path, os.W_OK):
238 print(_("{0} is not writable").format(path))
242 # if gnupg_homedir isn't set up, set it up
243 def init_gnupg(self):
244 if not os.path.exists(self.paths["gnupg_homedir"]):
245 print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
246 self.mkdir(self.paths["gnupg_homedir"])
249 def refresh_keyring(self, fingerprint=None):
250 if fingerprint is not None:
251 print("Refreshing local keyring... Missing key: " + fingerprint)
253 print("Refreshing local keyring...")
255 # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
256 p = subprocess.Popen(
262 self.paths["gnupg_homedir"],
266 "torbrowser@torproject.org",
268 stderr=subprocess.PIPE,
272 for output in p.stderr.readlines():
273 match = gnupg_import_ok_pattern.match(output)
274 if match and match.group(2) == "IMPORT_OK":
275 fingerprint = str(match.group(4))
276 if match.group(3) == "0":
277 print("Keyring refreshed successfully...")
278 print(" No key updates for key: " + fingerprint)
279 elif match.group(3) == "4":
280 print("Keyring refreshed successfully...")
281 print(" New signatures for key: " + fingerprint)
283 print("Keyring refreshed successfully...")
285 def import_key_and_check_status(self, key):
286 """Import a GnuPG key and check that the operation was successful.
287 :param str key: A string specifying the key's filepath from
290 :returns: ``True`` if the key is now within the keyring (or was
291 previously and hasn't changed). ``False`` otherwise.
293 with gpg.Context() as c:
295 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
298 impkey = self.paths["signing_keys"][key]
300 c.op_import(gpg.Data(file=impkey))
304 result = c.op_import_result()
305 if result and self.fingerprints[key] in result.imports[0].fpr:
311 def import_keys(self):
312 """Import all GnuPG keys.
314 :returns: ``True`` if all keys were successfully imported; ``False``
318 "tor_browser_developers",
320 all_imports_succeeded = True
323 imported = self.import_key_and_check_status(key)
327 "Could not import key with fingerprint: %s."
328 % self.fingerprints[key]
331 all_imports_succeeded = False
333 if not all_imports_succeeded:
334 print(_("Not all keys were imported successfully!"))
336 return all_imports_succeeded
339 def load_mirrors(self):
341 for srcfile in self.paths["mirrors_txt"]:
342 if not os.path.exists(srcfile):
344 for mirror in open(srcfile, "r").readlines():
345 if mirror.strip() not in self.mirrors:
346 self.mirrors.append(mirror.strip())
349 def load_settings(self):
351 "tbl_version": self.tbl_version,
353 "download_over_tor": False,
354 "tor_socks_address": "127.0.0.1:9050",
355 "mirror": self.default_mirror,
356 "force_en-US": False,
359 if os.path.isfile(self.paths["settings_file"]):
360 settings = json.load(open(self.paths["settings_file"]))
364 settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
366 # make sure settings file is up-to-date
367 for setting in default_settings:
368 if setting not in settings:
369 settings[setting] = default_settings[setting]
372 # make sure tor_socks_address doesn't start with 'tcp:'
373 if settings["tor_socks_address"].startswith("tcp:"):
374 settings["tor_socks_address"] = settings["tor_socks_address"][4:]
377 # make sure the version is current
378 if settings["tbl_version"] != self.tbl_version:
379 settings["tbl_version"] = self.tbl_version
382 self.settings = settings
386 # if settings file is still using old pickle format, convert to json
387 elif os.path.isfile(self.paths["settings_file_pickle"]):
388 self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
390 os.remove(self.paths["settings_file_pickle"])
394 self.settings = default_settings
398 def save_settings(self):
399 json.dump(self.settings, open(self.paths["settings_file"], "w"))