4 https://github.com/micahflee/torbrowser-launcher/
6 Copyright (c) 2013 Micah Lee <micah@micahflee.com>
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
34 gettext.install('torbrowser-launcher', '/usr/share/torbrowser-launcher/locale')
36 from twisted.internet import gtk2reactor
38 from twisted.internet import reactor
44 import os, subprocess, locale, urllib2, gobject, time, pickle, json, tarfile, psutil, hashlib, lzma
46 from twisted.web.client import Agent, RedirectAgent, ResponseDone, ResponseFailed
47 from twisted.web.http_headers import Headers
48 from twisted.internet.protocol import Protocol
49 from twisted.internet.ssl import ClientContextFactory
50 from twisted.internet.endpoints import TCP4ClientEndpoint
51 from twisted.internet.error import DNSLookupError
53 from txsocksx.client import SOCKS5ClientEndpoint
57 class TryStableException(Exception):
59 class TryDefaultMirrorException(Exception):
61 class DownloadErrorException(Exception):
64 class VerifyTorProjectCert(ClientContextFactory):
66 def __init__(self, torproject_pem):
67 self.torproject_ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open(torproject_pem, 'r').read())
69 def getContext(self, host, port):
70 ctx = ClientContextFactory.getContext(self)
71 ctx.set_verify_depth(0)
72 ctx.set_verify(OpenSSL.SSL.VERIFY_PEER | OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
75 def verifyHostname(self, connection, cert, errno, depth, preverifyOK):
76 return cert.digest('sha256') == self.torproject_ca.digest('sha256')
80 def __init__(self, tbl_version):
81 print _('Initializing Tor Browser Launcher')
82 self.tbl_version = tbl_version
85 self.available_versions = {
86 'stable': _('Tor Browser Bundle - stable'),
87 'alpha': _('Tor Browser Bundle - alpha')
89 self.default_mirror = 'https://www.torproject.org/dist/'
91 self.discover_arch_lang()
93 self.mkdir(self.paths['data_dir'])
96 self.mkdir(self.paths['download_dir'])
97 self.mkdir(self.paths['tbb'][self.settings['preferred']]['dir'])
100 # allow buttons to have icons
102 gtk_settings = gtk.settings_get_default()
103 gtk_settings.props.gtk_button_images = True
107 # discover the architecture and language
108 def discover_arch_lang(self):
109 # figure out the architecture
110 (sysname, nodename, release, version, machine) = os.uname()
111 self.architecture = 'x86_64' if '64' in platform.architecture()[0] else 'i686'
113 # figure out the language
114 available_languages = ['en-US', 'ar', 'de', 'es-ES', 'fa', 'fr', 'it', 'ko', 'nl', 'pl', 'pt-PT', 'ru', 'vi', 'zh-CN']
115 default_locale = locale.getdefaultlocale()[0]
116 if default_locale == None:
117 self.language = 'en-US'
119 self.language = default_locale.replace('_', '-')
120 if self.language not in available_languages:
121 self.language = self.language.split('-')[0]
122 if self.language not in available_languages:
123 for l in available_languages:
124 if l[0:2] == self.language:
126 # if language isn't available, default to english
127 if self.language not in available_languages:
128 self.language = 'en-US'
130 # build all relevant paths
131 def build_paths(self, tbb_version = None):
132 homedir = os.getenv('HOME')
134 homedir = '/tmp/.torbrowser-'+os.getenv('USER')
135 if os.path.exists(homedir) == False:
137 os.mkdir(homedir, 0700)
139 self.set_gui('error', _("Error creating {0}").format(homedir), [], False)
140 if not os.access(homedir, os.W_OK):
141 self.set_gui('error', _("{0} is not writable").format(homedir), [], False)
143 tbb_data = '%s/.torbrowser' % homedir
147 dirname = tbb_version.replace('-alpha-', 'a').replace('-beta-', 'b').replace('-rc-', 'rc')
148 if self.architecture == 'x86_64':
152 tarball_filename = 'tor-browser-'+arch+'-'+tbb_version+'_'+self.language+'.tar.xz'
155 self.paths['tarball_url'] = '{0}torbrowser/'+dirname+'/'+tarball_filename
158 self.paths['sha256_file'] = tbb_data+'/download/sha256sums.txt'
159 self.paths['sha256_sig_file'] = tbb_data+'/download/sha256sums.txt.asc'
160 self.paths['sha256_url'] = '{0}torbrowser/'+dirname+'/sha256sums.txt'
161 self.paths['sha256_sig_url'] = '{0}torbrowser/'+dirname+'/sha256sums.txt-mikeperry.asc'
163 self.paths['tarball_file'] = tbb_data+'/download/'+tarball_filename
164 self.paths['tarball_filename'] = tarball_filename
168 'tbl_bin': '/usr/bin/torbrowser-launcher',
169 'icon_file': '/usr/share/pixmaps/torbrowser80.xpm',
170 'torproject_pem': '/usr/share/torbrowser-launcher/torproject.pem',
171 'erinn_key': '/usr/share/torbrowser-launcher/erinn.asc',
172 'sebastian_key': '/usr/share/torbrowser-launcher/sebastian.asc',
173 'alexandre_key': '/usr/share/torbrowser-launcher/alexandre.asc',
174 'mike_key': '/usr/share/torbrowser-launcher/mike-2013-09.asc',
175 'mirrors_txt': ['/usr/share/torbrowser-launcher/mirrors.txt',
176 '/usr/local/share/torbrowser-launcher/mirrors.txt'],
177 'modem_sound': '/usr/share/torbrowser-launcher/modem.ogg',
178 'data_dir': tbb_data,
179 'download_dir': tbb_data+'/download',
180 'gnupg_homedir': tbb_data+'/gnupg_homedir',
181 'settings_file': tbb_data+'/settings',
182 'update_check_url': 'https://check.torproject.org/RecommendedTBBVersions',
183 'update_check_file': tbb_data+'/download/RecommendedTBBVersions',
186 'dir': tbb_data+'/tbb/stable/'+self.architecture,
187 'start': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
188 'firefox_bin': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Browser/firefox',
189 'firefox_profile': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
190 'versions': tbb_data+'/tbb/stable/'+self.architecture+'/tor-browser_'+self.language+'/Docs/sources/versions',
193 'dir': tbb_data+'/tbb/alpha/'+self.architecture,
194 'start': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/start-tor-browser',
195 'firefox_bin': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Browser/firefox',
196 'firefox_profile': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Data/profile',
197 'versions': tbb_data+'/tbb/alpha/'+self.architecture+'/tor-browser_'+self.language+'/Docs/sources/versions',
203 def mkdir(self, path):
205 if os.path.exists(path) == False:
206 os.makedirs(path, 0700)
209 print _("Cannot create directory {0}").format(path)
211 if not os.access(path, os.W_OK):
212 print _("{0} is not writable").format(path)
216 # if gnupg_homedir isn't set up, set it up
217 def init_gnupg(self):
218 if not os.path.exists(self.paths['gnupg_homedir']):
219 print _('Creating GnuPG homedir'), self.paths['gnupg_homedir']
220 self.mkdir(self.paths['gnupg_homedir'])
224 def import_keys(self):
225 print _('Importing keys')
226 subprocess.Popen(['/usr/bin/gpg', '--homedir', self.paths['gnupg_homedir'], '--import', self.paths['erinn_key'], self.paths['sebastian_key'], self.paths['alexandre_key'], self.paths['mike_key']]).wait()
229 def load_mirrors(self):
231 for srcfile in self.paths['mirrors_txt']:
232 if not os.path.exists(srcfile):
233 print "Warning: can't load mirrors from %s" % srcfile
235 for mirror in open(srcfile, 'r').readlines():
236 if mirror.strip() not in self.mirrors:
237 self.mirrors.append(mirror.strip())
240 def load_settings(self):
242 'tbl_version': self.tbl_version,
243 'preferred': 'stable',
244 'installed_version': {
252 'update_over_tor': True,
253 'check_for_updates': False,
254 'modem_sound': False,
255 'last_update_check_timestamp': 0,
256 'mirror': self.default_mirror
259 if os.path.isfile(self.paths['settings_file']):
260 settings = pickle.load(open(self.paths['settings_file']))
263 # settings migrations
264 if settings['tbl_version'] == '0.0.1':
265 print '0.0.1 migration'
266 self.settings = default_settings
267 self.settings['installed_version']['alpha'] = settings['installed_version']
271 self.mkdir(self.paths['tbb']['alpha']['dir'])
273 # make sure settings file is up-to-date
274 for setting in default_settings:
275 if setting not in settings:
276 settings[setting] = default_settings[setting]
279 # make sure the version is current
280 if settings['tbl_version'] != self.tbl_version:
281 settings['tbl_version'] = self.tbl_version
284 self.settings = settings
289 self.settings = default_settings
293 def save_settings(self):
294 pickle.dump(self.settings, open(self.paths['settings_file'], 'w'))
297 # get the process id of a program
298 def get_pid(self, bin_path, python = False):
301 for p in psutil.process_iter():
303 if p.pid != os.getpid():
306 if len(p.cmdline) > 1:
307 if 'python' in p.cmdline[0]:
310 if len(p.cmdline) > 0:
321 # bring program's x window to front
322 def bring_window_to_front(self, pid):
323 # figure out the window id
325 p = subprocess.Popen(['wmctrl', '-l', '-p'], stdout=subprocess.PIPE)
326 for line in p.stdout.readlines():
327 line_split = line.split()
328 cur_win_id = line_split[0]
329 cur_win_pid = int(line_split[2])
330 if cur_win_pid == pid:
335 subprocess.call(['wmctrl', '-i', '-a', win_id])
338 def __init__(self, common):
339 print _('Starting settings dialog')
343 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
344 self.window.set_title(_("Tor Browser Launcher Settings"))
345 self.window.set_icon_from_file(self.common.paths['icon_file'])
346 self.window.set_position(gtk.WIN_POS_CENTER)
347 self.window.set_border_width(10)
348 self.window.connect("delete_event", self.delete_event)
349 self.window.connect("destroy", self.destroy)
351 # build the rest of the UI
352 self.box = gtk.VBox(False, 10)
353 self.window.add(self.box)
356 self.hbox = gtk.HBox(False, 10)
357 self.box.pack_start(self.hbox, True, True, 0)
360 self.settings_box = gtk.VBox(False, 10)
361 self.hbox.pack_start(self.settings_box, True, True, 0)
362 self.settings_box.show()
364 self.labels_box = gtk.VBox(False, 10)
365 self.hbox.pack_start(self.labels_box, True, True, 0)
366 self.labels_box.show()
369 self.preferred_box = gtk.HBox(False, 10)
370 self.settings_box.pack_start(self.preferred_box, True, True, 0)
371 self.preferred_box.show()
373 self.preferred_label = gtk.Label(_('I prefer'))
374 self.preferred_label.set_line_wrap(True)
375 self.preferred_box.pack_start(self.preferred_label, True, True, 0)
376 self.preferred_label.show()
378 self.preferred_options = []
379 for i in self.common.available_versions:
380 self.preferred_options.append(self.common.available_versions[i])
381 self.preferred_options.sort()
383 self.preferred = gtk.combo_box_new_text()
384 for option in self.preferred_options:
385 self.preferred.append_text(option)
386 if self.common.settings['preferred'] in self.common.available_versions:
387 self.preferred.set_active( self.preferred_options.index(self.common.available_versions[self.common.settings['preferred']]) )
389 self.preferred.set_active(0)
390 self.preferred_box.pack_start(self.preferred, True, True, 0)
391 self.preferred.show()
394 # this feature isn't implemented yet (#8, #41), so commenting out the GUI for it
396 self.tor_update_checkbox = gtk.CheckButton(_("Download updates over Tor (recommended)"))
397 self.settings_box.pack_start(self.tor_update_checkbox, True, True, 0)
398 if self.common.settings['update_over_tor']:
399 self.tor_update_checkbox.set_active(True)
401 self.tor_update_checkbox.set_active(False)
402 self.tor_update_checkbox.show()
406 self.update_checkbox = gtk.CheckButton(_("Check for updates next launch"))
407 self.settings_box.pack_start(self.update_checkbox, True, True, 0)
408 if self.common.settings['check_for_updates']:
409 self.update_checkbox.set_active(True)
411 self.update_checkbox.set_active(False)
412 self.update_checkbox.show()
415 self.modem_checkbox = gtk.CheckButton(_("Play modem sound, because Tor is slow :]"))
416 self.settings_box.pack_start(self.modem_checkbox, True, True, 0)
417 if self.common.settings['modem_sound']:
418 self.modem_checkbox.set_active(True)
420 self.modem_checkbox.set_active(False)
421 self.modem_checkbox.show()
424 if(self.common.settings['installed_version'][self.common.settings['preferred']]):
425 self.label1 = gtk.Label(_('Installed version:\n{0}').format(self.common.settings['installed_version'][self.common.settings['preferred']]))
427 self.label1 = gtk.Label(_('Not installed'))
428 self.label1.set_line_wrap(True)
429 self.labels_box.pack_start(self.label1, True, True, 0)
432 if(self.common.settings['last_update_check_timestamp'] > 0):
433 self.label1 = gtk.Label(_('Last checked for updates:\n{0}').format(time.strftime("%B %d, %Y %I:%M %P", time.gmtime(self.common.settings['last_update_check_timestamp']))))
435 self.label1 = gtk.Label(_('Never checked for updates'))
436 self.label1.set_line_wrap(True)
437 self.labels_box.pack_start(self.label1, True, True, 0)
441 self.mirrors_box = gtk.HBox(False, 10)
442 self.box.pack_start(self.mirrors_box, True, True, 0)
443 self.mirrors_box.show()
445 self.mirrors_label = gtk.Label(_('Mirror'))
446 self.mirrors_label.set_line_wrap(True)
447 self.mirrors_box.pack_start(self.mirrors_label, True, True, 0)
448 self.mirrors_label.show()
450 self.mirrors = gtk.combo_box_new_text()
451 for mirror in self.common.mirrors:
452 self.mirrors.append_text(mirror)
453 if self.common.settings['mirror'] in self.common.mirrors:
454 self.mirrors.set_active( self.common.mirrors.index(self.common.settings['mirror']) )
456 self.preferred.set_active(0)
457 self.mirrors_box.pack_start(self.mirrors, True, True, 0)
461 self.button_box = gtk.HButtonBox()
462 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
463 self.box.pack_start(self.button_box, True, True, 0)
464 self.button_box.show()
466 # save and launch button
467 save_launch_image = gtk.Image()
468 save_launch_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
469 self.save_launch_button = gtk.Button(_("Launch Tor Browser"))
470 self.save_launch_button.set_image(save_launch_image)
471 self.save_launch_button.connect("clicked", self.save_launch, None)
472 self.button_box.add(self.save_launch_button)
473 self.save_launch_button.show()
475 # save and exit button
476 save_exit_image = gtk.Image()
477 save_exit_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
478 self.save_exit_button = gtk.Button(_("Save & Exit"))
479 self.save_exit_button.set_image(save_exit_image)
480 self.save_exit_button.connect("clicked", self.save_exit, None)
481 self.button_box.add(self.save_exit_button)
482 self.save_exit_button.show()
485 cancel_image = gtk.Image()
486 cancel_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
487 self.cancel_button = gtk.Button(_("Cancel"))
488 self.cancel_button.set_image(cancel_image)
489 self.cancel_button.connect("clicked", self.destroy, None)
490 self.button_box.add(self.cancel_button)
491 self.cancel_button.show()
500 def save_launch(self, widget, data=None):
502 p = subprocess.Popen([self.common.paths['tbl_bin']])
506 def save_exit(self, widget, data=None):
512 # figure out the selected preferred option
514 selected = self.preferred_options[self.preferred.get_active()]
515 for i in self.common.available_versions:
516 if self.common.available_versions[i] == selected:
519 self.common.settings['preferred'] = preferred
522 #self.common.settings['update_over_tor'] = self.tor_update_checkbox.get_active()
523 self.common.settings['check_for_updates'] = self.update_checkbox.get_active()
524 self.common.settings['modem_sound'] = self.modem_checkbox.get_active()
526 # figure out the selected mirror
527 self.common.settings['mirror'] = self.common.mirrors[self.mirrors.get_active()]
530 self.common.save_settings()
533 def delete_event(self, widget, event, data=None):
535 def destroy(self, widget, data=None):
540 def __init__(self, common):
541 print _('Starting launcher dialog')
545 self.set_gui(None, '', [])
546 self.launch_gui = True
547 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
549 # is firefox already running?
550 if self.common.settings['installed_version']:
551 firefox_pid = self.common.get_pid('./Browser/firefox')
553 print _('Firefox are is open, bringing to focus')
554 # bring firefox to front
555 self.common.bring_window_to_front(firefox_pid)
559 check_for_updates = False
560 if self.common.settings['check_for_updates']:
561 check_for_updates = True
563 if not check_for_updates:
564 # how long was it since the last update check?
565 # 86400 seconds = 24 hours
566 current_timestamp = int(time.time())
567 if current_timestamp - self.common.settings['last_update_check_timestamp'] >= 86400:
568 check_for_updates = True
570 if check_for_updates:
572 print 'Checking for update'
573 self.set_gui('task', _("Checking for Tor Browser update."),
574 ['download_update_check',
577 # no need to check for update
578 print _('Checked for update within 24 hours, skipping')
579 self.start_launcher()
583 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
584 self.window.set_title(_("Tor Browser"))
585 self.window.set_icon_from_file(self.common.paths['icon_file'])
586 self.window.set_position(gtk.WIN_POS_CENTER)
587 self.window.set_border_width(10)
588 self.window.connect("delete_event", self.delete_event)
589 self.window.connect("destroy", self.destroy)
591 # build the rest of the UI
594 # download or run TBB
595 def start_launcher(self):
596 # is TBB already installed?
597 latest_version = self.common.settings['latest_version'][self.common.settings['preferred']]
598 installed_version = self.common.settings['installed_version'][self.common.settings['preferred']]
600 # verify installed version for newer versions of TBB (#58)
601 if installed_version >= '3.0':
602 versions_filename = self.common.paths['tbb'][self.common.settings['preferred']]['versions']
603 if os.path.exists(versions_filename):
604 for line in open(versions_filename):
605 if 'TORBROWSER_VERSION' in line:
606 installed_version = line.lstrip('TORBROWSER_VERSION=').strip()
608 start = self.common.paths['tbb'][self.common.settings['preferred']]['start']
609 if os.path.isfile(start) and os.access(start, os.X_OK):
610 if installed_version == latest_version:
611 print _('Latest version of TBB is installed, launching')
612 # current version of tbb is installed, launch it
614 self.launch_gui = False
615 elif installed_version < latest_version:
616 print _('TBB is out of date, attempting to upgrade to {0}'.format(latest_version))
617 # there is a tbb upgrade available
618 self.set_gui('task', _("Your Tor Browser is out of date."),
620 'download_sha256_sig',
626 # for some reason the installed tbb is newer than the current version?
627 self.set_gui('error', _("Something is wrong. The version of Tor Browser Bundle you have installed is newer than the current version?"), [])
631 print _('TBB is not installed, attempting to install {0}'.format(latest_version))
632 self.set_gui('task', _("Downloading and installing Tor Browser."),
634 'download_sha256_sig',
640 # there are different GUIs that might appear, this sets which one we want
641 def set_gui(self, gui, message, tasks, autostart=True):
643 self.gui_message = message
644 self.gui_tasks = tasks
646 self.gui_autostart = autostart
648 # set all gtk variables to False
650 if hasattr(self, 'box') and hasattr(self.box, 'destroy'):
655 self.progressbar = False
656 self.button_box = False
657 self.start_button = False
658 self.exit_button = False
660 # build the application's UI
664 self.box = gtk.VBox(False, 20)
665 self.window.add(self.box)
667 if 'error' in self.gui:
669 self.label = gtk.Label( self.gui_message )
670 self.label.set_line_wrap(True)
671 self.box.pack_start(self.label, True, True, 0)
675 self.button_box = gtk.HButtonBox()
676 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
677 self.box.pack_start(self.button_box, True, True, 0)
678 self.button_box.show()
680 if self.gui != 'error':
682 yes_image = gtk.Image()
683 yes_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
684 self.yes_button = gtk.Button("Yes")
685 self.yes_button.set_image(yes_image)
686 if self.gui == 'error_try_stable':
687 self.yes_button.connect("clicked", self.try_stable, None)
688 elif self.gui == 'error_try_default_mirror':
689 self.yes_button.connect("clicked", self.try_default_mirror, None)
690 elif self.gui == 'error_try_tor':
691 self.yes_button.connect("clicked", self.try_tor, None)
692 self.button_box.add(self.yes_button)
693 self.yes_button.show()
696 exit_image = gtk.Image()
697 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
698 self.exit_button = gtk.Button("Exit")
699 self.exit_button.set_image(exit_image)
700 self.exit_button.connect("clicked", self.destroy, None)
701 self.button_box.add(self.exit_button)
702 self.exit_button.show()
704 elif self.gui == 'task':
706 self.label = gtk.Label( self.gui_message )
707 self.label.set_line_wrap(True)
708 self.box.pack_start(self.label, True, True, 0)
712 self.progressbar = gtk.ProgressBar(adjustment=None)
713 self.progressbar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
714 self.progressbar.set_pulse_step(0.01)
715 self.box.pack_start(self.progressbar, True, True, 0)
718 self.button_box = gtk.HButtonBox()
719 self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
720 self.box.pack_start(self.button_box, True, True, 0)
721 self.button_box.show()
724 start_image = gtk.Image()
725 start_image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
726 self.start_button = gtk.Button(_("Start"))
727 self.start_button.set_image(start_image)
728 self.start_button.connect("clicked", self.start, None)
729 self.button_box.add(self.start_button)
730 if not self.gui_autostart:
731 self.start_button.show()
734 exit_image = gtk.Image()
735 exit_image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
736 self.exit_button = gtk.Button(_("Exit"))
737 self.exit_button.set_image(exit_image)
738 self.exit_button.connect("clicked", self.destroy, None)
739 self.button_box.add(self.exit_button)
740 self.exit_button.show()
745 if self.gui_autostart:
748 # start button clicked, begin tasks
749 def start(self, widget, data=None):
750 # disable the start button
751 if self.start_button:
752 self.start_button.set_sensitive(False)
754 # start running tasks
757 # run the next task in the task list
761 if self.gui_task_i >= len(self.gui_tasks):
765 task = self.gui_tasks[self.gui_task_i]
767 # get ready for the next task
770 print _('Running task: {0}'.format(task))
771 if task == 'download_update_check':
772 print _('Downloading'), self.common.paths['update_check_url']
773 self.download('update check', self.common.paths['update_check_url'], self.common.paths['update_check_file'])
775 if task == 'attempt_update':
776 print _('Checking to see if update is needed')
777 self.attempt_update()
779 elif task == 'download_sha256':
780 print _('Downloading'), self.common.paths['sha256_url'].format(self.common.settings['mirror'])
781 self.download('signature', self.common.paths['sha256_url'], self.common.paths['sha256_file'])
783 elif task == 'download_sha256_sig':
784 print _('Downloading'), self.common.paths['sha256_sig_url'].format(self.common.settings['mirror'])
785 self.download('signature', self.common.paths['sha256_sig_url'], self.common.paths['sha256_sig_file'])
787 elif task == 'download_tarball_sig':
788 print _('Downloading'), self.common.paths['tarball_sig_url'].format(self.common.settings['mirror'])
789 self.download('signature', self.common.paths['tarball_sig_url'], self.common.paths['tarball_sig_file'])
791 elif task == 'download_tarball':
792 print _('Downloading'), self.common.paths['tarball_url'].format(self.common.settings['mirror'])
793 self.download('tarball', self.common.paths['tarball_url'], self.common.paths['tarball_file'])
795 elif task == 'verify':
796 print _('Verifying signature')
799 elif task == 'extract':
800 print _('Extracting'), self.common.paths['tarball_filename']
804 print _('Running'), self.common.paths['tbb'][self.common.settings['preferred']]['start']
807 elif task == 'start_over':
808 print _('Starting download over again')
811 def response_received(self, response):
812 class FileDownloader(Protocol):
813 def __init__(self, common, file, total, progress, done_cb):
817 self.progress = progress
818 self.all_done = done_cb
820 if response.code != 200:
823 if response.code == 404:
824 if common.settings['preferred'] == 'alpha' and common.language != 'en-US':
828 raise TryStableException(_("It looks like the alpha version of Tor Browser Bundle isn't available for your language. Would you like to try the stable version instead?"))
830 if common.settings['mirror'] != common.default_mirror:
831 raise TryDefaultMirrorException(_("Download Error: {0} {1}\n\nYou are currently using a non-default mirror:\n{2}\n\nWould you like to switch back to the default?").format(response.code, response.phrase, common.settings['mirror']))
833 raise DownloadErrorException(_("Download Error: {0} {1}").format(response.code, response.phrase))
835 def dataReceived(self, bytes):
836 self.file.write(bytes)
837 self.so_far += len(bytes)
838 percent = float(self.so_far) / float(self.total)
839 self.progress.set_fraction(percent)
840 amount = float(self.so_far)
842 for (size, unit) in [(1024 * 1024, "MiB"), (1024, "KiB")]:
845 amount = amount / float(size)
848 self.progress.set_text(_('Downloaded')+(' %2.1f%% (%2.1f %s)' % ((percent * 100.0), amount, units)))
850 def connectionLost(self, reason):
851 print _('Finished receiving body:'), reason.getErrorMessage()
852 self.all_done(reason)
854 dl = FileDownloader(self.common, self.file_download, response.length, self.progressbar, self.response_finished)
855 response.deliverBody(dl)
857 def response_finished(self, msg):
858 if msg.check(ResponseDone):
859 self.file_download.close()
860 delattr(self, 'current_download_path')
866 print "FINISHED", msg
867 ## FIXME handle errors
869 def download_error(self, f):
870 print _("Download error:"), f.value, type(f.value)
872 if isinstance(f.value, TryStableException):
873 f.trap(TryStableException)
874 self.set_gui('error_try_stable', str(f.value), [], False)
876 elif isinstance(f.value, TryDefaultMirrorException):
877 f.trap(TryDefaultMirrorException)
878 self.set_gui('error_try_default_mirror', str(f.value), [], False)
880 elif isinstance(f.value, DownloadErrorException):
881 f.trap(DownloadErrorException)
882 self.set_gui('error', str(f.value), [], False)
884 elif isinstance(f.value, DNSLookupError):
885 f.trap(DNSLookupError)
886 if common.settings['mirror'] != common.default_mirror:
887 self.set_gui('error_try_default_mirror', _("DNS Lookup Error\n\nYou are currently using a non-default mirror:\n{0}\n\nWould you like to switch back to the default?").format(common.settings['mirror']), [], False)
889 self.set_gui('error', str(f.value), [], False)
891 elif isinstance(f.value, ResponseFailed):
892 for reason in f.value.reasons:
893 if isinstance(reason.value, OpenSSL.SSL.Error):
894 # TODO: add the ability to report attack by posting bug to trac.torproject.org
895 if not self.common.settings['update_over_tor']:
896 self.set_gui('error_try_tor', _('The SSL certificate served by https://www.torproject.org is invalid! You may be under attack. Try the download again using Tor?'), [], False)
898 self.set_gui('error', _('The SSL certificate served by https://www.torproject.org is invalid! You may be under attack.'), [], False)
901 self.set_gui('error', _("Error starting download:\n\n{0}\n\nAre you connected to the internet?").format(f.value), [], False)
905 def download(self, name, url, path):
906 # keep track of current download
907 self.current_download_path = path
909 # initialize the progress bar
910 mirror_url = url.format(self.common.settings['mirror'])
911 self.progressbar.set_fraction(0)
912 self.progressbar.set_text(_('Downloading {0}').format(name))
913 self.progressbar.show()
916 # default mirror gets certificate pinning, only for requests that use the mirror
917 if self.common.settings['mirror'] == self.common.default_mirror and '{0}' in url:
918 agent = Agent(reactor, VerifyTorProjectCert(self.common.paths['torproject_pem']))
920 agent = Agent(reactor)
922 # actually, agent needs to follow redirect
923 agent = RedirectAgent(agent)
926 d = agent.request('GET', mirror_url,
927 Headers({'User-Agent': ['torbrowser-launcher']}),
930 self.file_download = open(path, 'w')
931 d.addCallback(self.response_received).addErrback(self.download_error)
933 if not reactor.running:
936 def try_stable(self, widget, data=None):
937 # change preferred to stable and relaunch TBL
938 self.common.settings['preferred'] = 'stable'
939 self.common.save_settings()
940 p = subprocess.Popen([self.common.paths['tbl_bin']])
943 def try_default_mirror(self, widget, data=None):
944 # change preferred to stable and relaunch TBL
945 self.common.settings['mirror'] = self.common.default_mirror
946 self.common.save_settings()
947 p = subprocess.Popen([self.common.paths['tbl_bin']])
950 def try_tor(self, widget, data=None):
951 # set update_over_tor to true and relaunch TBL
952 self.common.settings['update_over_tor'] = True
953 self.common.save_settings()
954 p = subprocess.Popen([self.common.paths['tbl_bin']])
957 def attempt_update(self):
958 # load the update check file
960 versions = json.load(open(self.common.paths['update_check_file']))
964 # filter linux versions
967 for version in versions:
968 if '-Linux' in version:
969 if 'alpha' in version or 'beta' in version or '-rc' in version:
970 valid_alphas.append(str(version))
972 valid_stables.append(str(version))
974 if len(valid_alphas):
975 latest_alpha = valid_alphas.pop()
977 if len(valid_stables):
978 latest_stable = valid_stables.pop()
980 if latest_stable or latest_alpha:
982 self.common.settings['latest_version']['stable'] = latest_stable[:-len('-Linux')]
984 self.common.settings['latest_version']['alpha'] = latest_alpha[:-len('-Linux')]
985 self.common.settings['last_update_check_timestamp'] = int(time.time())
986 self.common.settings['check_for_updates'] = False
987 self.common.save_settings()
988 self.common.build_paths(self.common.settings['latest_version'][self.common.settings['preferred']])
989 self.start_launcher()
992 # failed to find the latest version
993 self.set_gui('error', _("Error checking for updates."), [], False)
996 # not a valid JSON object
997 self.set_gui('error', _("Error checking for updates."), [], False)
1004 latest_version = self.common.settings['latest_version'][self.common.settings['preferred']]
1006 # initialize the progress bar
1007 self.progressbar.set_fraction(0)
1008 self.progressbar.set_text(_('Verifying Signature'))
1009 self.progressbar.show()
1012 # check the sha256 file's sig, and also take the sha256 of the tarball and compare
1013 p = subprocess.Popen(['/usr/bin/gpg', '--homedir', self.common.paths['gnupg_homedir'], '--verify', self.common.paths['sha256_sig_file']])
1014 self.pulse_until_process_exits(p)
1015 if p.returncode == 0:
1016 # compare with sha256 of the tarball
1017 tarball_sha256 = hashlib.sha256(open(self.common.paths['tarball_file'], 'r').read()).hexdigest()
1018 for line in open(self.common.paths['sha256_file'], 'r').readlines():
1019 if tarball_sha256.lower() in line.lower() and self.common.paths['tarball_filename'] in line:
1025 # TODO: add the ability to report attack by posting bug to trac.torproject.org
1026 self.set_gui('task', _("SIGNATURE VERIFICATION FAILED!\n\nYou might be under attack, or there might just be a networking problem. Click Start try the download again."), ['start_over'], False)
1030 if not reactor.running:
1034 # initialize the progress bar
1035 self.progressbar.set_fraction(0)
1036 self.progressbar.set_text(_('Installing'))
1037 self.progressbar.show()
1042 if self.common.paths['tarball_file'][-2:] == 'xz':
1043 # if tarball is .tar.xz
1044 xz = lzma.LZMAFile(self.common.paths['tarball_file'])
1045 tf = tarfile.open(fileobj=xz)
1046 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
1049 # if tarball is .tar.gz
1050 if tarfile.is_tarfile(self.common.paths['tarball_file']):
1051 tf = tarfile.open(self.common.paths['tarball_file'])
1052 tf.extractall(self.common.paths['tbb'][self.common.settings['preferred']]['dir'])
1058 self.set_gui('task', _("Tor Browser Launcher doesn't understand the file format of {0}".format(self.common.paths['tarball_file'])), ['start_over'], False)
1063 # installation is finished, so save installed_version
1064 self.common.settings['installed_version'] = self.common.settings['latest_version']
1065 self.common.save_settings()
1069 def run(self, run_next_task = True):
1070 subprocess.Popen([self.common.paths['tbb'][self.common.settings['preferred']]['start']])
1073 if self.common.settings['modem_sound']:
1076 sound = pygame.mixer.Sound(self.common.paths['modem_sound'])
1083 # make the progress bar pulse until process p (a Popen object) finishes
1084 def pulse_until_process_exits(self, p):
1085 while p.poll() == None:
1087 self.progressbar.pulse()
1090 # start over and download TBB again
1091 def start_over(self):
1092 self.label.set_text(_("Downloading Tor Browser Bundle over again."))
1093 self.gui_tasks = ['download_tarball', 'download_tarball_sig', 'verify', 'extract', 'run']
1098 def refresh_gtk(self):
1099 while gtk.events_pending():
1100 gtk.main_iteration(False)
1103 def delete_event(self, widget, event, data=None):
1105 def destroy(self, widget, data=None):
1106 if hasattr(self, 'file_download'):
1107 self.file_download.close()
1108 if hasattr(self, 'current_download_path'):
1109 os.remove(self.current_download_path)
1110 delattr(self, 'current_download_path')
1114 if __name__ == "__main__":
1115 tor_browser_launcher_version = open('/usr/share/torbrowser-launcher/version').read().strip()
1117 print _('Tor Browser Launcher')
1118 print _('By Micah Lee, licensed under GPLv3')
1119 print _('version {0}').format(tor_browser_launcher_version)
1120 print 'https://github.com/micahflee/torbrowser-launcher'
1122 common = TBLCommon(tor_browser_launcher_version)
1124 # is torbrowser-launcher already running?
1125 tbl_pid = common.get_pid(common.paths['tbl_bin'], True)
1127 print _('Tor Browser Launcher is already running (pid {0}), bringing to front').format(tbl_pid)
1128 common.bring_window_to_front(tbl_pid)
1131 if '-settings' in sys.argv:
1133 app = TBLSettings(common)
1137 app = TBLLauncher(common)