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 = [
113 default_locale = locale.getlocale()[0]
114 if default_locale is None:
115 self.language = "en-US"
117 self.language = default_locale.replace("_", "-")
118 if self.language not in available_languages:
119 self.language = self.language.split("-")[0]
120 if self.language not in available_languages:
121 for l in available_languages:
122 if l[0:2] == self.language:
124 # if language isn't available, default to english
125 if self.language not in available_languages:
126 self.language = "en-US"
128 # get value of environment variable, if it is not set return the default value
130 def get_env(var_name, default_value):
131 value = os.getenv(var_name)
133 value = default_value
136 # build all relevant paths
137 def build_paths(self, tbb_version=None):
138 homedir = os.getenv("HOME")
140 homedir = "/tmp/.torbrowser-" + os.getenv("USER")
141 if not os.path.exists(homedir):
143 os.mkdir(homedir, 0o700)
146 "error", _("Error creating {0}").format(homedir), [], False
148 if not os.access(homedir, os.W_OK):
149 self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
151 tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
152 tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
153 tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
154 old_tbb_data = '{0}/.torbrowser'.format(homedir)
158 if self.architecture == "x86_64":
163 if hasattr(self, "settings") and self.settings["force_en-US"]:
166 language = self.language
168 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
172 self.paths["tarball_url"] = (
173 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
175 self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
176 self.paths["tarball_filename"] = tarball_filename
179 self.paths["sig_url"] = (
180 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
182 self.paths["sig_file"] = (
183 tbb_cache + "/download/" + tarball_filename + ".asc"
185 self.paths["sig_filename"] = tarball_filename + ".asc"
188 "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,},
189 "old_data_dir": old_tbb_data,
190 "tbl_bin": sys.argv[0],
191 "icon_file": os.path.join(
192 os.path.dirname(SHARE), "pixmaps/torbrowser.png"
194 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
196 "tor_browser_developers": os.path.join(
197 SHARE, "tor-browser-developers.asc"
201 os.path.join(SHARE, "mirrors.txt"),
202 tbb_config + "/mirrors.txt",
204 "download_dir": tbb_cache + "/download",
205 "gnupg_homedir": tbb_local + "/gnupg_homedir",
206 "settings_file": tbb_config + "/settings.json",
207 "settings_file_pickle": tbb_config + "/settings",
208 "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
209 "version_check_file": tbb_cache + "/download/release.xml",
211 "changelog": tbb_local
216 + "/Browser/TorBrowser/Docs/ChangeLog.txt",
217 "dir": tbb_local + "/tbb/" + self.architecture,
228 + "/start-tor-browser.desktop",
232 # Add the expected fingerprint for imported keys:
233 self.fingerprints = {
234 "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
241 if not os.path.exists(path):
242 os.makedirs(path, 0o700)
245 print(_("Cannot create directory {0}").format(path))
247 if not os.access(path, os.W_OK):
248 print(_("{0} is not writable").format(path))
252 # if gnupg_homedir isn't set up, set it up
253 def init_gnupg(self):
254 if not os.path.exists(self.paths["gnupg_homedir"]):
255 print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
256 self.mkdir(self.paths["gnupg_homedir"])
259 def refresh_keyring(self, fingerprint=None):
260 if fingerprint is not None:
261 print("Refreshing local keyring... Missing key: " + fingerprint)
263 print("Refreshing local keyring...")
265 # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
266 p = subprocess.Popen(
272 self.paths["gnupg_homedir"],
276 "torbrowser@torproject.org",
278 stderr=subprocess.PIPE,
282 for output in p.stderr.readlines():
283 match = gnupg_import_ok_pattern.match(output)
284 if match and match.group(2) == "IMPORT_OK":
285 fingerprint = str(match.group(4))
286 if match.group(3) == "0":
287 print("Keyring refreshed successfully...")
288 print(" No key updates for key: " + fingerprint)
289 elif match.group(3) == "4":
290 print("Keyring refreshed successfully...")
291 print(" New signatures for key: " + fingerprint)
293 print("Keyring refreshed successfully...")
295 def import_key_and_check_status(self, key):
296 """Import a GnuPG key and check that the operation was successful.
297 :param str key: A string specifying the key's filepath from
300 :returns: ``True`` if the key is now within the keyring (or was
301 previously and hasn't changed). ``False`` otherwise.
303 with gpg.Context() as c:
305 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
308 impkey = self.paths["signing_keys"][key]
310 c.op_import(gpg.Data(file=impkey))
314 result = c.op_import_result()
315 if result and self.fingerprints[key] in result.imports[0].fpr:
321 def import_keys(self):
322 """Import all GnuPG keys.
324 :returns: ``True`` if all keys were successfully imported; ``False``
328 "tor_browser_developers",
330 all_imports_succeeded = True
333 imported = self.import_key_and_check_status(key)
337 "Could not import key with fingerprint: %s."
338 % self.fingerprints[key]
341 all_imports_succeeded = False
343 if not all_imports_succeeded:
344 print(_("Not all keys were imported successfully!"))
346 return all_imports_succeeded
349 def load_mirrors(self):
351 for srcfile in self.paths["mirrors_txt"]:
352 if not os.path.exists(srcfile):
354 for mirror in open(srcfile, "r").readlines():
355 if mirror.strip() not in self.mirrors:
356 self.mirrors.append(mirror.strip())
359 def load_settings(self):
361 "tbl_version": self.tbl_version,
363 "download_over_tor": False,
364 "tor_socks_address": "127.0.0.1:9050",
365 "mirror": self.default_mirror,
366 "force_en-US": False,
369 if os.path.isfile(self.paths["settings_file"]):
370 settings = json.load(open(self.paths["settings_file"]))
374 settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
376 # make sure settings file is up-to-date
377 for setting in default_settings:
378 if setting not in settings:
379 settings[setting] = default_settings[setting]
382 # make sure tor_socks_address doesn't start with 'tcp:'
383 if settings["tor_socks_address"].startswith("tcp:"):
384 settings["tor_socks_address"] = settings["tor_socks_address"][4:]
387 # make sure the version is current
388 if settings["tbl_version"] != self.tbl_version:
389 settings["tbl_version"] = self.tbl_version
392 self.settings = settings
396 # if settings file is still using old pickle format, convert to json
397 elif os.path.isfile(self.paths["settings_file_pickle"]):
398 self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
400 os.remove(self.paths["settings_file_pickle"])
404 self.settings = default_settings
408 def save_settings(self):
409 json.dump(self.settings, open(self.paths["settings_file"], "w"))