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})")
54 def __init__(self, tbl_version):
55 self.tbl_version = tbl_version
58 self.default_mirror = 'https://dist.torproject.org/'
59 self.discover_arch_lang()
61 for d in self.paths['dirs']:
62 self.mkdir(self.paths['dirs'][d])
65 self.mkdir(self.paths['download_dir'])
66 self.mkdir(self.paths['tbb']['dir'])
69 # discover the architecture and language
70 def discover_arch_lang(self):
71 # figure out the architecture
72 self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
74 # figure out the language
75 available_languages = ['ar', 'ca', 'da', 'de', 'en-US', 'es-ES', 'fa', 'fr', 'ga-IE', 'he', 'id', 'is', 'it', 'ja', 'ko', 'nb-NO', 'nl', 'pl', 'pt-BR', 'ru', 'sv-SE', 'tr', 'vi', 'zh-CN', 'zh-TW']
77 # a list of manually configured language fallback overriding
78 language_overrides = {
82 default_locale = locale.getlocale()[0]
83 if default_locale is None:
84 self.language = 'en-US'
86 self.language = default_locale.replace('_', '-')
87 if self.language in language_overrides:
88 self.language = language_overrides[self.language]
89 if self.language not in available_languages:
90 self.language = self.language.split('-')[0]
91 if self.language not in available_languages:
92 for l in available_languages:
93 if l[0:2] == self.language:
95 # if language isn't available, default to english
96 if self.language not in available_languages:
97 self.language = 'en-US'
99 # get value of environment variable, if it is not set return the default value
101 def get_env(var_name, default_value):
102 value = os.getenv(var_name)
104 value = default_value
107 # build all relevant paths
108 def build_paths(self, tbb_version=None):
109 homedir = os.getenv('HOME')
111 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
112 if not os.path.exists(homedir):
114 os.mkdir(homedir, 0o700)
116 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
117 if not os.access(homedir, os.W_OK):
118 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
120 tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir)))
121 tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir)))
122 tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir)))
123 old_tbb_data = '{0}/.torbrowser'.format(homedir)
127 if self.architecture == 'x86_64':
132 if hasattr(self, 'settings') and self.settings['force_en-US']:
135 language = self.language
136 tarball_filename = 'tor-browser-' + arch + '-' + tbb_version + '_' + language + '.tar.xz'
139 self.paths['tarball_url'] = '{0}torbrowser/' + tbb_version + '/' + tarball_filename
140 self.paths['tarball_file'] = tbb_cache + '/download/' + tarball_filename
141 self.paths['tarball_filename'] = tarball_filename
144 self.paths['sig_url'] = '{0}torbrowser/' + tbb_version + '/' + tarball_filename + '.asc'
145 self.paths['sig_file'] = tbb_cache + '/download/' + tarball_filename + '.asc'
146 self.paths['sig_filename'] = tarball_filename + '.asc'
150 'config': tbb_config,
154 'old_data_dir': old_tbb_data,
155 'tbl_bin': sys.argv[0],
156 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
157 'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
159 'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
161 'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
162 tbb_config + '/mirrors.txt'],
163 'download_dir': tbb_cache + '/download',
164 'gnupg_homedir': tbb_local + '/gnupg_homedir',
165 'settings_file': tbb_config + '/settings.json',
166 'settings_file_pickle': tbb_config + '/settings',
167 'version_check_url': 'https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US',
168 'version_check_file': tbb_cache + '/download/release.xml',
170 'changelog': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' +
171 self.language + '/Browser/TorBrowser/Docs/ChangeLog.txt',
172 'dir': tbb_local + '/tbb/' + self.architecture,
173 'dir_tbb': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' + self.language,
174 'start': tbb_local + '/tbb/' + self.architecture + '/tor-browser_' +
175 self.language + '/start-tor-browser.desktop'
179 # Add the expected fingerprint for imported keys:
180 self.fingerprints = {
181 'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
188 if not os.path.exists(path):
189 os.makedirs(path, 0o700)
192 print(_("Cannot create directory {0}").format(path))
194 if not os.access(path, os.W_OK):
195 print(_("{0} is not writable").format(path))
199 # if gnupg_homedir isn't set up, set it up
200 def init_gnupg(self):
201 if not os.path.exists(self.paths['gnupg_homedir']):
202 print(_('Creating GnuPG homedir'), self.paths['gnupg_homedir'])
203 self.mkdir(self.paths['gnupg_homedir'])
206 def refresh_keyring(self, fingerprint=None):
207 if fingerprint is not None:
208 print('Refreshing local keyring... Missing key: ' + fingerprint)
210 print('Refreshing local keyring...')
212 p = subprocess.Popen(['/usr/bin/gpg2', '--status-fd', '2',
213 '--homedir', self.paths['gnupg_homedir'],
214 '--keyserver', 'hkps://keys.openpgp.org',
215 '--refresh-keys'], stderr=subprocess.PIPE)
218 for output in p.stderr.readlines():
219 match = gnupg_import_ok_pattern.match(output)
220 if match and match.group(2) == 'IMPORT_OK':
221 fingerprint = str(match.group(4))
222 if match.group(3) == '0':
223 print('Keyring refreshed successfully...')
224 print(' No key updates for key: ' + fingerprint)
225 elif match.group(3) == '4':
226 print('Keyring refreshed successfully...')
227 print(' New signatures for key: ' + fingerprint)
229 print('Keyring refreshed successfully...')
231 def import_key_and_check_status(self, key):
232 """Import a GnuPG key and check that the operation was successful.
233 :param str key: A string specifying the key's filepath from
236 :returns: ``True`` if the key is now within the keyring (or was
237 previously and hasn't changed). ``False`` otherwise.
239 with gpg.Context() as c:
240 c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.paths['gnupg_homedir'])
242 impkey = self.paths['signing_keys'][key]
244 c.op_import(gpg.Data(file=impkey))
248 result = c.op_import_result()
249 if result and self.fingerprints[key] in result.imports[0].fpr:
255 def import_keys(self):
256 """Import all GnuPG keys.
258 :returns: ``True`` if all keys were successfully imported; ``False``
261 keys = ['tor_browser_developers', ]
262 all_imports_succeeded = True
265 imported = self.import_key_and_check_status(key)
267 print(_('Could not import key with fingerprint: %s.'
268 % self.fingerprints[key]))
269 all_imports_succeeded = False
271 if not all_imports_succeeded:
272 print(_('Not all keys were imported successfully!'))
274 return all_imports_succeeded
277 def load_mirrors(self):
279 for srcfile in self.paths['mirrors_txt']:
280 if not os.path.exists(srcfile):
282 for mirror in open(srcfile, 'r').readlines():
283 if mirror.strip() not in self.mirrors:
284 self.mirrors.append(mirror.strip())
287 def load_settings(self):
289 'tbl_version': self.tbl_version,
291 'download_over_tor': False,
292 'tor_socks_address': '127.0.0.1:9050',
293 'mirror': self.default_mirror,
294 'force_en-US': False,
297 if os.path.isfile(self.paths['settings_file']):
298 settings = json.load(open(self.paths['settings_file']))
302 settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
304 # make sure settings file is up-to-date
305 for setting in default_settings:
306 if setting not in settings:
307 settings[setting] = default_settings[setting]
310 # make sure tor_socks_address doesn't start with 'tcp:'
311 if settings['tor_socks_address'].startswith('tcp:'):
312 settings['tor_socks_address'] = settings['tor_socks_address'][4:]
315 # make sure the version is current
316 if settings['tbl_version'] != self.tbl_version:
317 settings['tbl_version'] = self.tbl_version
320 self.settings = settings
324 # if settings file is still using old pickle format, convert to json
325 elif os.path.isfile(self.paths['settings_file_pickle']):
326 self.settings = pickle.load(open(self.paths['settings_file_pickle']))
328 os.remove(self.paths['settings_file_pickle'])
332 self.settings = default_settings
336 def save_settings(self):
337 json.dump(self.settings, open(self.paths['settings_file'], 'w'))