]> git.lizzy.rs Git - torbrowser-launcher.git/blob - torbrowser_launcher/common.py
084c1a12b8c370b863eeb2cdc1fa840d5020c2be
[torbrowser-launcher.git] / torbrowser_launcher / common.py
1 """
2 Tor Browser Launcher
3 https://github.com/micahflee/torbrowser-launcher/
4
5 Copyright (c) 2013-2014 Micah Lee <micah@micahflee.com>
6
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
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
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.
27 """
28
29 import os, sys, platform, subprocess, locale, pickle, json, psutil, re
30
31 import pygtk
32 pygtk.require('2.0')
33 import gtk
34
35 SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
36
37 import gettext
38 gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
39
40 from twisted.internet import gtk2reactor
41 gtk2reactor.install()
42
43
44 # We're looking for output which:
45 #
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     "(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
51
52
53 class Common:
54
55     def __init__(self, tbl_version):
56         self.tbl_version = tbl_version
57
58         # initialize the app
59         self.default_mirror = 'https://www.torproject.org/dist/'
60         self.discover_arch_lang()
61         self.build_paths()
62         for d in self.paths['dirs']:
63             self.mkdir(self.paths['dirs'][d])
64         self.load_mirrors()
65         self.load_settings()
66         self.mkdir(self.paths['download_dir'])
67         self.mkdir(self.paths['tbb']['dir'])
68         self.init_gnupg()
69
70         # allow buttons to have icons
71         try:
72             gtk_settings = gtk.settings_get_default()
73             gtk_settings.props.gtk_button_images = True
74         except:
75             pass
76
77     # discover the architecture and language
78     def discover_arch_lang(self):
79         # figure out the architecture
80         self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
81
82         # figure out the language
83         available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
84         default_locale = locale.getlocale(locale.LC_MESSAGES)[0]
85         if default_locale is None:
86             self.language = 'en-US'
87         else:
88             self.language = default_locale.replace('_', '-')
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:
94                             self.language = l
95             # if language isn't available, default to english
96             if self.language not in available_languages:
97                 self.language = 'en-US'
98
99     # build all relevant paths
100     def build_paths(self, tbb_version=None):
101         homedir = os.getenv('HOME')
102         if not homedir:
103             homedir = '/tmp/.torbrowser-'+os.getenv('USER')
104             if not os.path.exists(homedir):
105                 try:
106                     os.mkdir(homedir, 0700)
107                 except:
108                     self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
109         if not os.access(homedir, os.W_OK):
110             self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
111
112         tbb_config = '{0}/.config/torbrowser'.format(homedir)
113         tbb_cache = '{0}/.cache/torbrowser'.format(homedir)
114         tbb_local = '{0}/.local/share/torbrowser'.format(homedir)
115         old_tbb_data = '{0}/.torbrowser'.format(homedir)
116
117         if tbb_version:
118             # tarball filename
119             if self.architecture == 'x86_64':
120                 arch = 'linux64'
121             else:
122                 arch = 'linux32'
123
124             if hasattr(self, 'settings') and self.settings['force_en-US']:
125                 language = 'en-US'
126             else:
127                 language = self.language
128             tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+language+'.tar.xz'
129
130             # tarball
131             self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
132             self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename
133             self.paths['tarball_filename'] = tarball_filename
134
135             # sig
136             self.paths['sig_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename+'.asc'
137             self.paths['sig_file'] = tbb_cache+'/download/'+tarball_filename+'.asc'
138             self.paths['sig_filename'] = tarball_filename+'.asc'
139         else:
140             self.paths = {
141                 'dirs': {
142                     'config': tbb_config,
143                     'cache': tbb_cache,
144                     'local': tbb_local,
145                 },
146                 'old_data_dir': old_tbb_data,
147                 'tbl_bin': sys.argv[0],
148                 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
149                 'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
150                 'signing_keys': {
151                     'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
152                 },
153                 'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
154                                 tbb_config+'/mirrors.txt'],
155                 'modem_sound': os.path.join(SHARE, 'modem.ogg'),
156                 'download_dir': tbb_cache+'/download',
157                 'gnupg_homedir': tbb_local+'/gnupg_homedir',
158                 'settings_file': tbb_config+'/settings.json',
159                 'settings_file_pickle': tbb_config+'/settings',
160                 'version_check_url': 'https://dist.torproject.org/torbrowser/update_2/release/Linux_x86_64-gcc3/x/en-US',
161                 'version_check_file': tbb_cache+'/download/release.xml',
162                 'tbb': {
163                     'dir': tbb_local+'/tbb/'+self.architecture,
164                     'dir_tbb': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language,
165                     'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser.desktop',
166                     'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Browser/TorBrowser/Docs/sources/versions',
167                 },
168             }
169
170         # Add the expected fingerprint for imported keys:
171         self.fingerprints = {
172             'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
173         }
174
175     # create a directory
176     @staticmethod
177     def mkdir(path):
178         try:
179             if not os.path.exists(path):
180                 os.makedirs(path, 0700)
181                 return True
182         except:
183             print _("Cannot create directory {0}").format(path)
184             return False
185         if not os.access(path, os.W_OK):
186             print _("{0} is not writable").format(path)
187             return False
188         return True
189
190     # if gnupg_homedir isn't set up, set it up
191     def init_gnupg(self):
192         if not os.path.exists(self.paths['gnupg_homedir']):
193             print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
194             self.mkdir(self.paths['gnupg_homedir'])
195         self.import_keys()
196
197     def import_key_and_check_status(self, key):
198         """Import a GnuPG key and check that the operation was successful.
199
200         :param str key: A string specifying the key's filepath from
201             ``Common.paths``, as well as its fingerprint in
202             ``Common.fingerprints``.
203         :rtype: bool
204         :returns: ``True`` if the key is now within the keyring (or was
205             previously and hasn't changed). ``False`` otherwise.
206         """
207         success = False
208
209         p = subprocess.Popen(['/usr/bin/gpg', '--status-fd', '2',
210                               '--homedir', self.paths['gnupg_homedir'],
211                               '--import', self.paths['signing_keys'][key]],
212                              stderr=subprocess.PIPE)
213         p.wait()
214
215         for output in p.stderr.readlines():
216             match = gnupg_import_ok_pattern.match(output)
217             if match:
218                 # The output must match everything in the
219                 # ``gnupg_import_ok_pattern``, as well as the expected fingerprint:
220                 if match.group().find(self.fingerprints[key]) >= 0:
221                     success = True
222                     break
223
224         return success
225
226     # import gpg keys
227     def import_keys(self):
228         """Import all GnuPG keys.
229
230         :rtype: bool
231         :returns: ``True`` if all keys were successfully imported; ``False``
232             otherwise.
233         """
234         keys = ['tor_browser_developers',]
235         all_imports_succeeded = True
236
237         print _('Importing keys')
238         for key in keys:
239             imported = self.import_key_and_check_status(key)
240             if not imported:
241                 print _('Could not import key with fingerprint: %s.'
242                         % self.fingerprints[key])
243                 all_imports_succeeded = False
244
245         if all_imports_succeeded:
246             print _('Successfully imported all keys.')
247         else:
248             print _('Not all keys were imported successfully!')
249
250         return all_imports_succeeded
251
252     # load mirrors
253     def load_mirrors(self):
254         self.mirrors = []
255         for srcfile in self.paths['mirrors_txt']:
256             if not os.path.exists(srcfile):
257                 continue
258             for mirror in open(srcfile, 'r').readlines():
259                 if mirror.strip() not in self.mirrors:
260                     self.mirrors.append(mirror.strip())
261
262     # load settings
263     def load_settings(self):
264         default_settings = {
265             'tbl_version': self.tbl_version,
266             'installed': False,
267             'download_over_tor': False,
268             'modem_sound': False,
269             'tor_socks_address': 'tcp:127.0.0.1:9050',
270             'mirror': self.default_mirror,
271             'force_en-US': False,
272         }
273
274         if os.path.isfile(self.paths['settings_file']):
275             settings = json.load(open(self.paths['settings_file']))
276             resave = False
277
278             # detect installed
279             settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
280
281             # make sure settings file is up-to-date
282             for setting in default_settings:
283                 if setting not in settings:
284                     settings[setting] = default_settings[setting]
285                     resave = True
286
287             # make sure the version is current
288             if settings['tbl_version'] != self.tbl_version:
289                 settings['tbl_version'] = self.tbl_version
290                 resave = True
291
292             self.settings = settings
293             if resave:
294                 self.save_settings()
295
296         # if settings file is still using old pickle format, convert to json
297         elif os.path.isfile(self.paths['settings_file_pickle']):
298             self.settings = pickle.load(open(self.paths['settings_file_pickle']))
299             self.save_settings()
300             os.remove(self.paths['settings_file_pickle'])
301             self.load_settings()
302
303         else:
304             self.settings = default_settings
305             self.save_settings()
306
307     # save settings
308     def save_settings(self):
309         json.dump(self.settings, open(self.paths['settings_file'], 'w'))
310         return True