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 locale.setlocale(locale.LC_MESSAGES, '')
104 default_locale = locale.getlocale(locale.LC_MESSAGES)[0]
105 if default_locale is None:
106 self.language = "en-US"
108 self.language = default_locale.replace("_", "-")
109 if self.language not in available_languages:
110 self.language = self.language.split("-")[0]
111 if self.language not in available_languages:
112 for l in available_languages:
113 if l[0:2] == self.language:
115 # if language isn't available, default to english
116 if self.language not in available_languages:
117 self.language = "en-US"
119 # get value of environment variable, if it is not set return the default value
121 def get_env(var_name, default_value):
122 value = os.getenv(var_name)
124 value = default_value
127 # build all relevant paths
128 def build_paths(self, tbb_version=None):
129 homedir = os.getenv("HOME")
131 homedir = "/tmp/.torbrowser-" + os.getenv("USER")
132 if not os.path.exists(homedir):
134 os.mkdir(homedir, 0o700)
137 "error", _("Error creating {0}").format(homedir), [], False
139 if not os.access(homedir, os.W_OK):
140 self.set_gui("error", _("{0} is not writable").format(homedir), [], False)
142 tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
143 tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
144 tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
145 old_tbb_data = '{0}/.torbrowser'.format(homedir)
149 if self.architecture == "x86_64":
154 if hasattr(self, "settings") and self.settings["force_en-US"]:
157 language = self.language
159 "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz"
163 self.paths["tarball_url"] = (
164 "{0}torbrowser/" + tbb_version + "/" + tarball_filename
166 self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename
167 self.paths["tarball_filename"] = tarball_filename
170 self.paths["sig_url"] = (
171 "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ".asc"
173 self.paths["sig_file"] = (
174 tbb_cache + "/download/" + tarball_filename + ".asc"
176 self.paths["sig_filename"] = tarball_filename + ".asc"
179 "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,},
180 "old_data_dir": old_tbb_data,
181 "tbl_bin": sys.argv[0],
182 "icon_file": os.path.join(
183 os.path.dirname(SHARE), "pixmaps/torbrowser.png"
185 "torproject_pem": os.path.join(SHARE, "torproject.pem"),
187 "tor_browser_developers": os.path.join(
188 SHARE, "tor-browser-developers.asc"
192 os.path.join(SHARE, "mirrors.txt"),
193 tbb_config + "/mirrors.txt",
195 "download_dir": tbb_cache + "/download",
196 "gnupg_homedir": tbb_local + "/gnupg_homedir",
197 "settings_file": tbb_config + "/settings.json",
198 "settings_file_pickle": tbb_config + "/settings",
199 "version_check_url": "https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US",
200 "version_check_file": tbb_cache + "/download/release.xml",
202 "changelog": tbb_local
207 + "/Browser/TorBrowser/Docs/ChangeLog.txt",
208 "dir": tbb_local + "/tbb/" + self.architecture,
219 + "/start-tor-browser.desktop",
223 # Add the expected fingerprint for imported keys:
224 self.fingerprints = {
225 "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
232 if not os.path.exists(path):
233 os.makedirs(path, 0o700)
236 print(_("Cannot create directory {0}").format(path))
238 if not os.access(path, os.W_OK):
239 print(_("{0} is not writable").format(path))
243 # if gnupg_homedir isn't set up, set it up
244 def init_gnupg(self):
245 if not os.path.exists(self.paths["gnupg_homedir"]):
246 print(_("Creating GnuPG homedir"), self.paths["gnupg_homedir"])
247 self.mkdir(self.paths["gnupg_homedir"])
250 def refresh_keyring(self, fingerprint=None):
251 if fingerprint is not None:
252 print("Refreshing local keyring... Missing key: " + fingerprint)
254 print("Refreshing local keyring...")
256 # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/
257 p = subprocess.Popen(
263 self.paths["gnupg_homedir"],
267 "torbrowser@torproject.org",
269 stderr=subprocess.PIPE,
273 for output in p.stderr.readlines():
274 match = gnupg_import_ok_pattern.match(output)
275 if match and match.group(2) == "IMPORT_OK":
276 fingerprint = str(match.group(4))
277 if match.group(3) == "0":
278 print("Keyring refreshed successfully...")
279 print(" No key updates for key: " + fingerprint)
280 elif match.group(3) == "4":
281 print("Keyring refreshed successfully...")
282 print(" New signatures for key: " + fingerprint)
284 print("Keyring refreshed successfully...")
286 def import_key_and_check_status(self, key):
287 """Import a GnuPG key and check that the operation was successful.
288 :param str key: A string specifying the key's filepath from
291 :returns: ``True`` if the key is now within the keyring (or was
292 previously and hasn't changed). ``False`` otherwise.
294 with gpg.Context() as c:
296 gpg.constants.protocol.OpenPGP, home_dir=self.paths["gnupg_homedir"]
299 impkey = self.paths["signing_keys"][key]
301 c.op_import(gpg.Data(file=impkey))
305 result = c.op_import_result()
306 if result and self.fingerprints[key] in result.imports[0].fpr:
312 def import_keys(self):
313 """Import all GnuPG keys.
315 :returns: ``True`` if all keys were successfully imported; ``False``
319 "tor_browser_developers",
321 all_imports_succeeded = True
324 imported = self.import_key_and_check_status(key)
328 "Could not import key with fingerprint: %s."
329 % self.fingerprints[key]
332 all_imports_succeeded = False
334 if not all_imports_succeeded:
335 print(_("Not all keys were imported successfully!"))
337 return all_imports_succeeded
340 def load_mirrors(self):
342 for srcfile in self.paths["mirrors_txt"]:
343 if not os.path.exists(srcfile):
345 for mirror in open(srcfile, "r").readlines():
346 if mirror.strip() not in self.mirrors:
347 self.mirrors.append(mirror.strip())
350 def load_settings(self):
352 "tbl_version": self.tbl_version,
354 "download_over_tor": False,
355 "tor_socks_address": "127.0.0.1:9050",
356 "mirror": self.default_mirror,
357 "force_en-US": False,
360 if os.path.isfile(self.paths["settings_file"]):
361 settings = json.load(open(self.paths["settings_file"]))
365 settings["installed"] = os.path.isfile(self.paths["tbb"]["start"])
367 # make sure settings file is up-to-date
368 for setting in default_settings:
369 if setting not in settings:
370 settings[setting] = default_settings[setting]
373 # make sure tor_socks_address doesn't start with 'tcp:'
374 if settings["tor_socks_address"].startswith("tcp:"):
375 settings["tor_socks_address"] = settings["tor_socks_address"][4:]
378 # make sure the version is current
379 if settings["tbl_version"] != self.tbl_version:
380 settings["tbl_version"] = self.tbl_version
383 self.settings = settings
387 # if settings file is still using old pickle format, convert to json
388 elif os.path.isfile(self.paths["settings_file_pickle"]):
389 self.settings = pickle.load(open(self.paths["settings_file_pickle"]))
391 os.remove(self.paths["settings_file_pickle"])
395 self.settings = default_settings
399 def save_settings(self):
400 json.dump(self.settings, open(self.paths["settings_file"], "w"))