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 = [
114 default_locale = locale.getlocale()[0]
115 if default_locale is None:
116 self.language = "en-US"
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:
125 # if language isn't available, default to english
126 if self.language not in available_languages:
127 self.language = "en-US"
129 # get value of environment variable, if it is not set return the default value
131 def get_env(var_name, default_value):
132 value = os.getenv(var_name)
134 value = default_value
137 # build all relevant paths
138 def build_paths(self, tbb_version=None):
139 homedir = os.getenv("HOME")
141 homedir = "/tmp/.torbrowser-" + os.getenv("USER")
142 if not os.path.exists(homedir):
144 os.mkdir(homedir, 0o700)
147 "error", _("Error creating {0}").format(homedir), [], False
149 if not os.access(homedir, os.W_OK):
150 self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
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)
159 if self.architecture == "x86_64":
164 if hasattr(self, "settings") and self.settings["force_en-US"]:
167 language = self.language
169 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
173 self.paths["tarball_url"] = (
174 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
176 self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
177 self.paths["tarball_filename"] = tarball_filename
180 self.paths["sig_url"] = (
181 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
183 self.paths["sig_file"] = (
184 tbb_cache + "/download/" + tarball_filename + ".asc"
186 self.paths["sig_filename"] = tarball_filename + ".asc"
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"
195 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
197 "tor_browser_developers": os.path.join(
198 SHARE, "tor-browser-developers.asc"
202 os.path.join(SHARE, "mirrors.txt"),
203 tbb_config + "/mirrors.txt",
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",
212 "changelog": tbb_local
217 + "/Browser/TorBrowser/Docs/ChangeLog.txt",
218 "dir": tbb_local + "/tbb/" + self.architecture,
229 + "/start-tor-browser.desktop",
233 # Add the expected fingerprint for imported keys:
234 self.fingerprints = {
235 "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
242 if not os.path.exists(path):
243 os.makedirs(path, 0o700)
246 print(_("Cannot create directory {0}").format(path))
248 if not os.access(path, os.W_OK):
249 print(_("{0} is not writable").format(path))
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"])
260 def refresh_keyring(self, fingerprint=None):
261 if fingerprint is not None:
262 print("Refreshing local keyring... Missing key: " + fingerprint)
264 print("Refreshing local keyring...")
266 # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
267 p = subprocess.Popen(
273 self.paths["gnupg_homedir"],
277 "torbrowser@torproject.org",
279 stderr=subprocess.PIPE,
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)
294 print("Keyring refreshed successfully...")
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
301 :returns: ``True`` if the key is now within the keyring (or was
302 previously and hasn't changed). ``False`` otherwise.
304 with gpg.Context() as c:
306 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
309 impkey = self.paths["signing_keys"][key]
311 c.op_import(gpg.Data(file=impkey))
315 result = c.op_import_result()
316 if result and self.fingerprints[key] in result.imports[0].fpr:
322 def import_keys(self):
323 """Import all GnuPG keys.
325 :returns: ``True`` if all keys were successfully imported; ``False``
329 "tor_browser_developers",
331 all_imports_succeeded = True
334 imported = self.import_key_and_check_status(key)
338 "Could not import key with fingerprint: %s."
339 % self.fingerprints[key]
342 all_imports_succeeded = False
344 if not all_imports_succeeded:
345 print(_("Not all keys were imported successfully!"))
347 return all_imports_succeeded
350 def load_mirrors(self):
352 for srcfile in self.paths["mirrors_txt"]:
353 if not os.path.exists(srcfile):
355 for mirror in open(srcfile, "r").readlines():
356 if mirror.strip() not in self.mirrors:
357 self.mirrors.append(mirror.strip())
360 def load_settings(self):
362 "tbl_version": self.tbl_version,
364 "download_over_tor": False,
365 "tor_socks_address": "127.0.0.1:9050",
366 "mirror": self.default_mirror,
367 "force_en-US": False,
370 if os.path.isfile(self.paths["settings_file"]):
371 settings = json.load(open(self.paths["settings_file"]))
375 settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
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]
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:]
388 # make sure the version is current
389 if settings["tbl_version"] != self.tbl_version:
390 settings["tbl_version"] = self.tbl_version
393 self.settings = settings
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"]))
401 os.remove(self.paths["settings_file_pickle"])
405 self.settings = default_settings
409 def save_settings(self):
410 json.dump(self.settings, open(self.paths["settings_file"], "w"))