Tor Browser Launcher
https://github.com/micahflee/torbrowser-launcher/
-Copyright (c) 2013-2014 Micah Lee <micah@micahflee.com>
+Copyright (c) 2013-2017 Micah Lee <micah@micahflee.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
OTHER DEALINGS IN THE SOFTWARE.
"""
-import os, sys, platform, subprocess, locale, pickle, json, psutil
+from __future__ import print_function
+
+import os
+import sys
+import platform
+import subprocess
+import locale
+import pickle
+import json
+import re
+
+try:
+ import gpg
+ gpgme_support = True
+except ImportError:
+ gpgme_support = False
import pygtk
pygtk.require('2.0')
SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher')
import gettext
-gettext.install('torbrowser-launcher', os.path.join(SHARE, 'locale'))
+gettext.install('torbrowser-launcher')
from twisted.internet import gtk2reactor
gtk2reactor.install()
+# We're looking for output which:
+#
+# 1. The first portion must be `[GNUPG:] IMPORT_OK`
+# 2. The second must be an integer between [0, 15], inclusive
+# 3. The third must be an uppercased hex-encoded 160-bit fingerprint
+gnupg_import_ok_pattern = re.compile(
+ "(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})")
+
+
class Common:
def __init__(self, tbl_version):
# figure out the language
available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
- default_locale = locale.getdefaultlocale()[0]
+ default_locale = locale.getlocale(locale.LC_MESSAGES)[0]
if default_locale is None:
self.language = 'en-US'
else:
homedir = '/tmp/.torbrowser-'+os.getenv('USER')
if not os.path.exists(homedir):
try:
- os.mkdir(homedir, 0700)
+ os.mkdir(homedir, 0o700)
except:
self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
if not os.access(homedir, os.W_OK):
arch = 'linux64'
else:
arch = 'linux32'
- tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+self.language+'.tar.xz'
+
+ if hasattr(self, 'settings') and self.settings['force_en-US']:
+ language = 'en-US'
+ else:
+ language = self.language
+ tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+language+'.tar.xz'
# tarball
self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename
self.paths['tarball_filename'] = tarball_filename
# sig
- self.paths['sha256_file'] = tbb_cache+'/download/sha256sums.txt'
- self.paths['sha256_sig_file'] = tbb_cache+'/download/sha256sums.txt.asc'
- self.paths['sha256_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt'
- self.paths['sha256_sig_url'] = '{0}torbrowser/'+tbb_version+'/sha256sums.txt.asc'
+ self.paths['sig_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename+'.asc'
+ self.paths['sig_file'] = tbb_cache+'/download/'+tarball_filename+'.asc'
+ self.paths['sig_filename'] = tarball_filename+'.asc'
else:
self.paths = {
'dirs': {
},
'old_data_dir': old_tbb_data,
'tbl_bin': sys.argv[0],
- 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser80.xpm'),
+ 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'),
'torproject_pem': os.path.join(SHARE, 'torproject.pem'),
- 'signing_keys': [os.path.join(SHARE, 'tor-browser-developers.asc')],
+ 'keyserver_ca': os.path.join(SHARE, 'sks-keyservers.netCA.pem'),
+ 'signing_keys': {
+ 'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc')
+ },
'mirrors_txt': [os.path.join(SHARE, 'mirrors.txt'),
tbb_config+'/mirrors.txt'],
'modem_sound': os.path.join(SHARE, 'modem.ogg'),
'gnupg_homedir': tbb_local+'/gnupg_homedir',
'settings_file': tbb_config+'/settings.json',
'settings_file_pickle': tbb_config+'/settings',
- 'update_check_url': 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions',
- 'update_check_file': tbb_cache+'/download/RecommendedTBBVersions',
+ 'version_check_url': 'https://aus1.torproject.org/torbrowser/update_3/release/Linux_x86_64-gcc3/x/en-US',
+ 'version_check_file': tbb_cache+'/download/release.xml',
'tbb': {
+ 'changelog': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Browser/TorBrowser/Docs/ChangeLog.txt',
'dir': tbb_local+'/tbb/'+self.architecture,
'dir_tbb': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language,
'start': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser.desktop',
- 'versions': tbb_local+'/tbb/'+self.architecture+'/tor-browser_'+self.language+'/Browser/TorBrowser/Docs/sources/versions',
},
}
+ # Add the expected fingerprint for imported keys:
+ self.fingerprints = {
+ 'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290'
+ }
+
# create a directory
@staticmethod
def mkdir(path):
try:
if not os.path.exists(path):
- os.makedirs(path, 0700)
+ os.makedirs(path, 0o700)
return True
except:
- print _("Cannot create directory {0}").format(path)
+ print(_("Cannot create directory {0}").format(path))
return False
if not os.access(path, os.W_OK):
- print _("{0} is not writable").format(path)
+ print(_("{0} is not writable").format(path))
return False
return True
# if gnupg_homedir isn't set up, set it up
def init_gnupg(self):
if not os.path.exists(self.paths['gnupg_homedir']):
- print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
+ print(_('Creating GnuPG homedir'), self.paths['gnupg_homedir'])
self.mkdir(self.paths['gnupg_homedir'])
self.import_keys()
+ def refresh_keyring(self, fingerprint=None):
+ if fingerprint is not None:
+ print('Refreshing local keyring... Missing key: ' + fingerprint)
+ else:
+ print('Refreshing local keyring...')
+
+ p = subprocess.Popen(['/usr/bin/gpg', '--status-fd', '2',
+ '--homedir', self.paths['gnupg_homedir'],
+ '--keyserver', 'hkps://hkps.pool.sks-keyservers.net',
+ '--keyserver-options', 'ca-cert-file=' + self.paths['keyserver_ca']
+ + ',include-revoked,no-honor-keyserver-url,no-honor-pka-record',
+ '--refresh-keys'], stderr=subprocess.PIPE)
+ p.wait()
+
+ for output in p.stderr.readlines():
+ match = gnupg_import_ok_pattern.match(output)
+ if match and match.group(2) == 'IMPORT_OK':
+ fingerprint = str(match.group(4))
+ if match.group(3) == '0':
+ print('Keyring refreshed successfully...')
+ print(' No key updates for key: ' + fingerprint)
+ elif match.group(3) == '4':
+ print('Keyring refreshed successfully...')
+ print(' New signatures for key: ' + fingerprint)
+ else:
+ print('Keyring refreshed successfully...')
+
+ def import_key_and_check_status(self, key):
+ """Import a GnuPG key and check that the operation was successful.
+ :param str key: A string specifying the key's filepath from
+ ``Common.paths``
+ :rtype: bool
+ :returns: ``True`` if the key is now within the keyring (or was
+ previously and hasn't changed). ``False`` otherwise.
+ """
+ if gpgme_support:
+ with gpg.Context() as c:
+ c.set_engine_info(gpg.constants.protocol.OpenPGP, home_dir=self.paths['gnupg_homedir'])
+
+ impkey = self.paths['signing_keys'][key]
+ try:
+ c.op_import(gpg.Data(file=impkey))
+ except:
+ return False
+ else:
+ result = c.op_import_result()
+ if result and self.fingerprints[key] in result.imports[0].fpr:
+ return True
+ else:
+ return False
+ else:
+ success = False
+
+ p = subprocess.Popen(['/usr/bin/gpg', '--status-fd', '2',
+ '--homedir', self.paths['gnupg_homedir'],
+ '--import', self.paths['signing_keys'][key]],
+ stderr=subprocess.PIPE)
+ p.wait()
+
+ for output in p.stderr.readlines():
+ match = gnupg_import_ok_pattern.match(output)
+ if match:
+ if match.group().find(self.fingerprints[key]) >= 0:
+ success = True
+ break
+
+ return success
+
# import gpg keys
def import_keys(self):
- for key in self.paths['signing_keys']:
- subprocess.Popen(['/usr/bin/gpg', '--quiet', '--homedir', self.paths['gnupg_homedir'], '--import', key]).wait()
+ """Import all GnuPG keys.
+ :rtype: bool
+ :returns: ``True`` if all keys were successfully imported; ``False``
+ otherwise.
+ """
+ keys = ['tor_browser_developers',]
+ all_imports_succeeded = True
+
+ for key in keys:
+ imported = self.import_key_and_check_status(key)
+ if not imported:
+ print(_('Could not import key with fingerprint: %s.'
+ % self.fingerprints[key]))
+ all_imports_succeeded = False
+
+ if not all_imports_succeeded:
+ print(_('Not all keys were imported successfully!'))
+
+ self.refresh_keyring()
+ return all_imports_succeeded
# load mirrors
def load_mirrors(self):
def load_settings(self):
default_settings = {
'tbl_version': self.tbl_version,
- 'installed_version': False,
- 'latest_version': '0',
- 'update_over_tor': True,
- 'check_for_updates': False,
+ 'installed': False,
+ 'download_over_tor': False,
'modem_sound': False,
- 'last_update_check_timestamp': 0,
- 'mirror': self.default_mirror
+ 'tor_socks_address': 'tcp:127.0.0.1:9050',
+ 'mirror': self.default_mirror,
+ 'force_en-US': False,
}
if os.path.isfile(self.paths['settings_file']):
settings = json.load(open(self.paths['settings_file']))
resave = False
+ # detect installed
+ settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
+
# make sure settings file is up-to-date
for setting in default_settings:
if setting not in settings:
def save_settings(self):
json.dump(self.settings, open(self.paths['settings_file'], 'w'))
return True
-
- # get the process id of a program
- @staticmethod
- def get_pid(bin_path, python=False):
- pid = None
-
- for p in psutil.process_iter():
- try:
- if p.pid != os.getpid():
- exe = None
- if python:
- if len(p.cmdline) > 1:
- if 'python' in p.cmdline[0]:
- exe = p.cmdline[1]
- else:
- if len(p.cmdline) > 0:
- exe = p.cmdline[0]
-
- if exe == bin_path:
- pid = p.pid
-
- except:
- pass
-
- return pid
-
- # bring program's x window to front
- @staticmethod
- def bring_window_to_front(pid):
- # figure out the window id
- win_id = None
- p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
- for line in p.stdout.readlines():
- line_split = line.split()
- cur_win_id = line_split[0]
- cur_win_pid = int(line_split[2])
- if cur_win_pid == pid:
- win_id = cur_win_id
-
- # bring to front
- if win_id:
- subprocess.call(['wmctrl', '-i', '-a', win_id])