]> git.lizzy.rs Git - torbrowser-launcher.git/blobdiff - torbrowser_launcher/common.py
Merge branch 'silence-tor-browser-apparmor-logs' of https://github.com/intrigeri...
[torbrowser-launcher.git] / torbrowser_launcher / common.py
index e5844b974eb1129c1ff1051a5d8aafe0297444dd..a3047868a7092ff65a65184f415e0cc7d6692003 100644 (file)
@@ -2,7 +2,7 @@
 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
@@ -26,7 +26,22 @@ 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, 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')
@@ -35,11 +50,20 @@ import gtk
 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):
@@ -71,7 +95,7 @@ class Common:
 
         # 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:
@@ -93,7 +117,7 @@ class Common:
             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):
@@ -110,7 +134,12 @@ class Common:
                 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
@@ -118,10 +147,9 @@ class Common:
             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': {
@@ -131,51 +159,146 @@ class Common:
                 },
                 '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, 'erinn.asc'), 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'),
                 'download_dir': tbb_cache+'/download',
                 'gnupg_homedir': tbb_local+'/gnupg_homedir',
-                'settings_file': tbb_config+'/settings',
-                'update_check_url': 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions',
-                'update_check_file': tbb_cache+'/download/RecommendedTBBVersions',
+                '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',
-                    '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):
@@ -191,32 +314,20 @@ class Common:
     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 = pickle.load(open(self.paths['settings_file']))
+            settings = json.load(open(self.paths['settings_file']))
             resave = False
 
-            # settings migrations
-            if settings['tbl_version'] <= '0.1.0':
-                print '0.1.0 migration'
-                settings['installed_version'] = settings['installed_version']['stable']
-                settings['latest_version'] = settings['latest_version']['stable']
-                resave = True
-
-                # make new tbb folder
-                self.mkdir(self.paths['tbb']['dir'])
-                old_tbb_dir = self.paths['old_data_dir']+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language
-                new_tbb_dir = self.paths['tbb']['dir']+'/tor-browser_'+self.language
-                if os.path.isdir(old_tbb_dir):
-                    os.rename(old_tbb_dir, new_tbb_dir)
+            # detect installed
+            settings['installed'] = os.path.isfile(self.paths['tbb']['start'])
 
             # make sure settings file is up-to-date
             for setting in default_settings:
@@ -233,53 +344,18 @@ class Common:
             if resave:
                 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']))
+            self.save_settings()
+            os.remove(self.paths['settings_file_pickle'])
+            self.load_settings()
+
         else:
             self.settings = default_settings
             self.save_settings()
 
     # save settings
     def save_settings(self):
-        pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
+        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])