X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=torbrowser_launcher%2Fcommon.py;h=757f4a124f4d4c5dc5c316b1e3def4f6c96e513a;hb=a85a8997025c592c199fd4d286f54055fff86df1;hp=3b6c2cdf581a1407b3aacc79bbb74fb83fc713ea;hpb=ff13666970f14a847e52399c56cb99f8635ca5b5;p=torbrowser-launcher.git diff --git a/torbrowser_launcher/common.py b/torbrowser_launcher/common.py index 3b6c2cd..757f4a1 100644 --- a/torbrowser_launcher/common.py +++ b/torbrowser_launcher/common.py @@ -26,140 +26,213 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import os, sys, platform, subprocess, locale, pickle, json, re, gpg - -import pygtk -pygtk.require('2.0') -import gtk +import os +import sys +import platform +import subprocess +import locale +import pickle +import json +import re +import gettext +import gpg -SHARE = os.getenv('TBL_SHARE', sys.prefix+'/share/torbrowser-launcher') +SHARE = os.getenv("TBL_SHARE", sys.prefix + "/share") + "/torbrowser-launcher" -import gettext -gettext.install('torbrowser-launcher') +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( + b"(\[GNUPG\:\]) (IMPORT_OK) ([0-9]|[1]?[0-5]) ([A-F0-9]{40})" +) -class Common: +class Common(object): def __init__(self, tbl_version): self.tbl_version = tbl_version # initialize the app - self.default_mirror = 'https://www.torproject.org/dist/' + self.default_mirror = "https://dist.torproject.org/" self.discover_arch_lang() self.build_paths() - for d in self.paths['dirs']: - self.mkdir(self.paths['dirs'][d]) + for d in self.paths["dirs"]: + self.mkdir(self.paths["dirs"][d]) self.load_mirrors() self.load_settings() - self.mkdir(self.paths['download_dir']) - self.mkdir(self.paths['tbb']['dir']) + self.mkdir(self.paths["download_dir"]) + self.mkdir(self.paths["tbb"]["dir"]) self.init_gnupg() - # allow buttons to have icons - try: - gtk_settings = gtk.settings_get_default() - gtk_settings.props.gtk_button_images = True - except: - pass - # discover the architecture and language def discover_arch_lang(self): # figure out the architecture - self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686' + self.architecture = "x86_64" if "64" in platform.architecture()[0] else "i686" # 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.getlocale(locale.LC_MESSAGES)[0] + available_languages = [ + "ar", + "ca", + "cs", + "da", + "de", + "el", + "en-US", + "es-AR", + "es-ES", + "fa", + "fr", + "ga-IE", + "he", + "hu", + "id", + "is", + "it", + "ja", + "ka", + "ko", + "lt", + "mk", + "ms", + "my", + "nb-NO", + "nl", + "pl", + "pt-BR", + "ro", + "ru", + "sv-SE", + "th", + "tr", + "vi", + "zh-CN", + "zh-TW", + ] + default_locale = locale.getlocale()[0] if default_locale is None: - self.language = 'en-US' + self.language = "en-US" else: - self.language = default_locale.replace('_', '-') + self.language = default_locale.replace("_", "-") if self.language not in available_languages: - self.language = self.language.split('-')[0] + self.language = self.language.split("-")[0] if self.language not in available_languages: for l in available_languages: if l[0:2] == self.language: self.language = l # if language isn't available, default to english if self.language not in available_languages: - self.language = 'en-US' + self.language = "en-US" + + # get value of environment variable, if it is not set return the default value + @staticmethod + def get_env(var_name, default_value): + value = os.getenv(var_name) + if not value: + value = default_value + return value # build all relevant paths def build_paths(self, tbb_version=None): - homedir = os.getenv('HOME') + homedir = os.getenv("HOME") if not homedir: - homedir = '/tmp/.torbrowser-'+os.getenv('USER') + 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) + self.set_gui( + "error", _("Error creating {0}").format(homedir), [], False + ) if not os.access(homedir, os.W_OK): - self.set_gui('error', _("{0} is not writable").format(homedir), [], False) + self.set_gui("error", _("{0} is not writable").format(homedir), [], False) - tbb_config = '{0}/.config/torbrowser'.format(homedir) - tbb_cache = '{0}/.cache/torbrowser'.format(homedir) - tbb_local = '{0}/.local/share/torbrowser'.format(homedir) + tbb_config = '{0}/torbrowser'.format(self.get_env('XDG_CONFIG_HOME', '{0}/.config'.format(homedir))) + tbb_cache = '{0}/torbrowser'.format(self.get_env('XDG_CACHE_HOME', '{0}/.cache'.format(homedir))) + tbb_local = '{0}/torbrowser'.format(self.get_env('XDG_DATA_HOME', '{0}/.local/share'.format(homedir))) old_tbb_data = '{0}/.torbrowser'.format(homedir) if tbb_version: # tarball filename - if self.architecture == 'x86_64': - arch = 'linux64' + if self.architecture == "x86_64": + arch = "linux64" else: - arch = 'linux32' + arch = "linux32" - if hasattr(self, 'settings') and self.settings['force_en-US']: - language = 'en-US' + 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_filename = ( + "tor-browser-" + arch + "-" + tbb_version + "_" + language + ".tar.xz" + ) # tarball - self.paths['tarball_url'] = '{0}torbrowser/'+tbb_version+'/'+tarball_filename - self.paths['tarball_file'] = tbb_cache+'/download/'+tarball_filename - self.paths['tarball_filename'] = tarball_filename + self.paths["tarball_url"] = ( + "{0}torbrowser/" + tbb_version + "/" + tarball_filename + ) + self.paths["tarball_file"] = tbb_cache + "/download/" + tarball_filename + self.paths["tarball_filename"] = tarball_filename # sig - 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' + 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': { - 'config': tbb_config, - 'cache': tbb_cache, - 'local': tbb_local, - }, - 'old_data_dir': old_tbb_data, - 'tbl_bin': sys.argv[0], - 'icon_file': os.path.join(os.path.dirname(SHARE), 'pixmaps/torbrowser.png'), - 'torproject_pem': os.path.join(SHARE, 'torproject.pem'), - 'signing_keys': { - 'tor_browser_developers': os.path.join(SHARE, 'tor-browser-developers.asc') + "dirs": {"config": tbb_config, "cache": tbb_cache, "local": tbb_local,}, + "old_data_dir": old_tbb_data, + "tbl_bin": sys.argv[0], + "icon_file": os.path.join( + os.path.dirname(SHARE), "pixmaps/torbrowser.png" + ), + "torproject_pem": os.path.join(SHARE, "torproject.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'), - 'download_dir': tbb_cache+'/download', - 'gnupg_homedir': tbb_local+'/gnupg_homedir', - 'settings_file': tbb_config+'/settings.json', - 'settings_file_pickle': tbb_config+'/settings', - 'version_check_url': 'https://dist.torproject.org/torbrowser/update_2/release/Linux_x86_64-gcc3/x/en-US', - 'version_check_file': tbb_cache+'/download/release.xml', - 'tbb': { - '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', + "mirrors_txt": [ + os.path.join(SHARE, "mirrors.txt"), + tbb_config + "/mirrors.txt", + ], + "download_dir": tbb_cache + "/download", + "gnupg_homedir": tbb_local + "/gnupg_homedir", + "settings_file": tbb_config + "/settings.json", + "settings_file_pickle": tbb_config + "/settings", + "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", }, } # Add the expected fingerprint for imported keys: self.fingerprints = { - 'tor_browser_developers': 'EF6E286DDA85EA2A4BA7DE684E2C6E8793298290' + "tor_browser_developers": "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290" } # create a directory @@ -167,23 +240,59 @@ class Common: 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'] - self.mkdir(self.paths['gnupg_homedir']) + if not os.path.exists(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...") + + # Fetch key from wkd, as per https://support.torproject.org/tbb/how-to-verify-signature/ + p = subprocess.Popen( + [ + "gpg2", + "--status-fd", + "2", + "--homedir", + self.paths["gnupg_homedir"], + "--auto-key-locate", + "nodefault,wkd", + "--locate-keys", + "torbrowser@torproject.org", + ], + 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 @@ -193,19 +302,21 @@ class Common: previously and hasn't changed). ``False`` otherwise. """ 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] - if os.path.isfile(impkey): + 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)) - else: - print _("Signing key not found") - - result = c.op_import_result() - if (result and self.fingerprints[key] in result.imports[0].fpr): - return True - else: + 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 # import gpg keys def import_keys(self): @@ -214,49 +325,54 @@ class Common: :returns: ``True`` if all keys were successfully imported; ``False`` otherwise. """ - keys = ['tor_browser_developers',] + 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]) + 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!') + print(_("Not all keys were imported successfully!")) return all_imports_succeeded # load mirrors def load_mirrors(self): self.mirrors = [] - for srcfile in self.paths['mirrors_txt']: + for srcfile in self.paths["mirrors_txt"]: if not os.path.exists(srcfile): continue - for mirror in open(srcfile, 'r').readlines(): + for mirror in open(srcfile, "r").readlines(): if mirror.strip() not in self.mirrors: self.mirrors.append(mirror.strip()) # load settings def load_settings(self): default_settings = { - 'tbl_version': self.tbl_version, - 'installed': False, - 'download_over_tor': False, - 'modem_sound': False, - 'tor_socks_address': 'tcp:127.0.0.1:9050', - 'mirror': self.default_mirror, - 'force_en-US': False, + "tbl_version": self.tbl_version, + "installed": False, + "download_over_tor": False, + "tor_socks_address": "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'])) + 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']) + settings["installed"] = os.path.isfile(self.paths["tbb"]["start"]) # make sure settings file is up-to-date for setting in default_settings: @@ -264,9 +380,14 @@ class Common: settings[setting] = default_settings[setting] resave = True + # make sure tor_socks_address doesn't start with 'tcp:' + if settings["tor_socks_address"].startswith("tcp:"): + settings["tor_socks_address"] = settings["tor_socks_address"][4:] + resave = True + # make sure the version is current - if settings['tbl_version'] != self.tbl_version: - settings['tbl_version'] = self.tbl_version + if settings["tbl_version"] != self.tbl_version: + settings["tbl_version"] = self.tbl_version resave = True self.settings = settings @@ -274,10 +395,10 @@ class Common: self.save_settings() # if settings file is still using old pickle format, convert to json - elif os.path.isfile(self.paths['settings_file_pickle']): - self.settings = pickle.load(open(self.paths['settings_file_pickle'])) + elif os.path.isfile(self.paths["settings_file_pickle"]): + self.settings = pickle.load(open(self.paths["settings_file_pickle"])) self.save_settings() - os.remove(self.paths['settings_file_pickle']) + os.remove(self.paths["settings_file_pickle"]) self.load_settings() else: @@ -286,5 +407,5 @@ class Common: # save settings def save_settings(self): - json.dump(self.settings, open(self.paths['settings_file'], 'w')) + json.dump(self.settings, open(self.paths["settings_file"], "w")) return True