diff options
author | kali <kali@leap.se> | 2012-07-22 21:10:15 -0700 |
---|---|---|
committer | kali <kali@leap.se> | 2012-07-22 21:10:15 -0700 |
commit | c46d8da153ac658c8bd145376e22b1218db1090a (patch) | |
tree | 0943a4a866d9f3b1bc590c1c23f810ca13635f9e /src/leap |
initial import
Diffstat (limited to 'src/leap')
-rw-r--r-- | src/leap/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/app.py | 41 | ||||
-rw-r--r-- | src/leap/baseapp/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/baseapp/config.py | 40 | ||||
-rw-r--r-- | src/leap/baseapp/mainwindow.py | 398 | ||||
-rw-r--r-- | src/leap/eip/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/eip/conductor.py | 272 | ||||
-rw-r--r-- | src/leap/eip/vpnmanager.py | 262 | ||||
-rw-r--r-- | src/leap/eip/vpnwatcher.py | 169 | ||||
-rw-r--r-- | src/leap/gui/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/gui/mainwindow_rc.py | 789 | ||||
-rw-r--r-- | src/leap/tests/fakeclient.py | 63 | ||||
-rw-r--r-- | src/leap/tests/mocks/__init__.py | 1 | ||||
-rw-r--r-- | src/leap/tests/mocks/manager.py | 20 | ||||
-rw-r--r-- | src/leap/utils/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/utils/coroutines.py | 107 | ||||
-rw-r--r-- | src/leap/utils/leap_argparse.py | 20 |
17 files changed, 2182 insertions, 0 deletions
diff --git a/src/leap/__init__.py b/src/leap/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/__init__.py diff --git a/src/leap/app.py b/src/leap/app.py new file mode 100644 index 00000000..0a61fd4f --- /dev/null +++ b/src/leap/app.py @@ -0,0 +1,41 @@ +import logging +# This is only needed for Python v2 but is harmless for Python v3. +import sip +sip.setapi('QVariant', 2) +from PyQt4.QtGui import (QApplication, QSystemTrayIcon, QMessageBox) + +from leap.baseapp.mainwindow import LeapWindow + +logger = logging.getLogger(name=__name__) + + +def main(): + """ + launches the main event loop + long live to the (hidden) leap window! + """ + import sys + from leap.utils import leap_argparse + parser, opts = leap_argparse.init_leapc_args() + debug = getattr(opts, 'debug', False) + + #XXX get debug level and set logger accordingly + if debug: + logger.debug('args: ', opts) + + app = QApplication(sys.argv) + + if not QSystemTrayIcon.isSystemTrayAvailable(): + QMessageBox.critical(None, "Systray", + "I couldn't detect any \ +system tray on this system.") + sys.exit(1) + if not debug: + QApplication.setQuitOnLastWindowClosed(False) + + window = LeapWindow(opts) + window.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() diff --git a/src/leap/baseapp/__init__.py b/src/leap/baseapp/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/baseapp/__init__.py diff --git a/src/leap/baseapp/config.py b/src/leap/baseapp/config.py new file mode 100644 index 00000000..efdb4726 --- /dev/null +++ b/src/leap/baseapp/config.py @@ -0,0 +1,40 @@ +import ConfigParser +import os + + +def get_config(config_file=None): + """ + temporary method for getting configs, + mainly for early stage development process. + in the future we will get preferences + from the storage api + """ + config = ConfigParser.ConfigParser() + #config.readfp(open('defaults.cfg')) + #XXX does this work on win / mac also??? + conf_path_list = ['eip.cfg', # XXX build a + # proper path with platform-specific places + # XXX make .config/foo + os.path.expanduser('~/.eip.cfg')] + if config_file: + config.readfp(config_file) + else: + config.read(conf_path_list) + return config + + +# XXX wrapper around config? to get default values + +def get_with_defaults(config, section, option): + if config.has_option(section, option): + return config.get(section, option) + else: + # XXX lookup in defaults dict??? + pass + + +def get_vpn_stdout_mockup(): + command = "python" + args = ["-u", "-c", "from eip_client import fakeclient;\ +fakeclient.write_output()"] + return command, args diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py new file mode 100644 index 00000000..68b6de8f --- /dev/null +++ b/src/leap/baseapp/mainwindow.py @@ -0,0 +1,398 @@ +# vim: set fileencoding=utf-8 : +#!/usr/bin/env python +import logging +import time +logger = logging.getLogger(name=__name__) + +from PyQt4.QtGui import (QMainWindow, QWidget, QVBoxLayout, QMessageBox, + QSystemTrayIcon, QGroupBox, QLabel, QPixmap, + QHBoxLayout, QIcon, + QPushButton, QGridLayout, QAction, QMenu, + QTextBrowser, qApp) +from PyQt4.QtCore import (pyqtSlot, pyqtSignal, QTimer) + +from leap.gui import mainwindow_rc +from leap.eip.conductor import EIPConductor + + +class LeapWindow(QMainWindow): + #XXX tbd: refactor into model / view / controller + #and put in its own modules... + + newLogLine = pyqtSignal([str]) + statusChange = pyqtSignal([object]) + + def __init__(self, opts): + super(LeapWindow, self).__init__() + self.debugmode = getattr(opts, 'debug', False) + + self.vpn_service_started = False + + self.createWindowHeader() + self.createIconGroupBox() + + self.createActions() + self.createTrayIcon() + if self.debugmode: + self.createLogBrowser() + + # create timer + self.timer = QTimer() + + # bind signals + + self.trayIcon.activated.connect(self.iconActivated) + self.newLogLine.connect(self.onLoggerNewLine) + self.statusChange.connect(self.onStatusChange) + self.timer.timeout.connect(self.onTimerTick) + + widget = QWidget() + self.setCentralWidget(widget) + + # add widgets to layout + mainLayout = QVBoxLayout() + mainLayout.addWidget(self.headerBox) + mainLayout.addWidget(self.statusIconBox) + if self.debugmode: + mainLayout.addWidget(self.statusBox) + mainLayout.addWidget(self.loggerBox) + widget.setLayout(mainLayout) + + # + # conductor is in charge of all + # vpn-related configuration / monitoring. + # we pass a tuple of signals that will be + # triggered when status changes. + # + config_file = getattr(opts, 'config_file', None) + self.conductor = EIPConductor( + watcher_cb=self.newLogLine.emit, + config_file=config_file, + status_signals=(self.statusChange.emit, )) + + self.trayIcon.show() + + self.setWindowTitle("Leap") + self.resize(400, 300) + + self.set_statusbarMessage('ready') + + if self.conductor.autostart: + self.start_or_stopVPN() + + def closeEvent(self, event): + """ + redefines close event (persistent window behaviour) + """ + if self.trayIcon.isVisible() and not self.debugmode: + QMessageBox.information(self, "Systray", + "The program will keep running " + "in the system tray. To " + "terminate the program, choose " + "<b>Quit</b> in the " + "context menu of the system tray entry.") + self.hide() + event.ignore() + if self.debugmode: + self.cleanupAndQuit() + + def setIcon(self, name): + icon = self.Icons.get(name) + self.trayIcon.setIcon(icon) + self.setWindowIcon(icon) + + def setToolTip(self): + """ + get readable status and place it on systray tooltip + """ + status = self.conductor.status.get_readable_status() + self.trayIcon.setToolTip(status) + + def iconActivated(self, reason): + """ + handles left click, left double click + showing the trayicon menu + """ + #XXX there's a bug here! + #menu shows on (0,0) corner first time, + #until double clicked at least once. + if reason in (QSystemTrayIcon.Trigger, + QSystemTrayIcon.DoubleClick): + self.trayIconMenu.show() + + def createWindowHeader(self): + """ + description lines for main window + """ + #XXX good candidate to refactor out! :) + self.headerBox = QGroupBox() + self.headerLabel = QLabel("<font size=40><b>E</b>ncryption \ +<b>I</b>nternet <b>P</b>roxy</font>") + self.headerLabelSub = QLabel("<i>trust your \ +technolust</i>") + + pixmap = QPixmap(':/images/leapfrog.jpg') + frog_lbl = QLabel() + frog_lbl.setPixmap(pixmap) + + headerLayout = QHBoxLayout() + headerLayout.addWidget(frog_lbl) + headerLayout.addWidget(self.headerLabel) + headerLayout.addWidget(self.headerLabelSub) + headerLayout.addStretch() + self.headerBox.setLayout(headerLayout) + + def getIcon(self, icon_name): + # XXX get from connection dict + icons = {'disconnected': 0, + 'connecting': 1, + 'connected': 2} + return icons.get(icon_name, None) + + def createIconGroupBox(self): + """ + dummy icongroupbox + (to be removed from here -- reference only) + """ + icons = { + 'disconnected': ':/images/conn_error.png', + 'connecting': ':/images/conn_connecting.png', + 'connected': ':/images/conn_connected.png' + } + con_widgets = { + 'disconnected': QLabel(), + 'connecting': QLabel(), + 'connected': QLabel(), + } + con_widgets['disconnected'].setPixmap( + QPixmap(icons['disconnected'])) + con_widgets['connecting'].setPixmap( + QPixmap(icons['connecting'])) + con_widgets['connected'].setPixmap( + QPixmap(icons['connected'])), + self.ConnectionWidgets = con_widgets + + con_icons = { + 'disconnected': QIcon(icons['disconnected']), + 'connecting': QIcon(icons['connecting']), + 'connected': QIcon(icons['connected']) + } + self.Icons = con_icons + + self.statusIconBox = QGroupBox("Connection Status") + statusIconLayout = QHBoxLayout() + statusIconLayout.addWidget(self.ConnectionWidgets['disconnected']) + statusIconLayout.addWidget(self.ConnectionWidgets['connecting']) + statusIconLayout.addWidget(self.ConnectionWidgets['connected']) + statusIconLayout.itemAt(1).widget().hide() + statusIconLayout.itemAt(2).widget().hide() + self.statusIconBox.setLayout(statusIconLayout) + + def createActions(self): + """ + creates actions to be binded to tray icon + """ + self.connectVPNAction = QAction("Connect to &VPN", self, + triggered=self.hide) + # XXX change action name on (dis)connect + self.dis_connectAction = QAction("&(Dis)connect", self, + triggered=self.start_or_stopVPN) + self.minimizeAction = QAction("Mi&nimize", self, + triggered=self.hide) + self.maximizeAction = QAction("Ma&ximize", self, + triggered=self.showMaximized) + self.restoreAction = QAction("&Restore", self, + triggered=self.showNormal) + self.quitAction = QAction("&Quit", self, + triggered=self.cleanupAndQuit) + + def createTrayIcon(self): + """ + creates the tray icon + """ + self.trayIconMenu = QMenu(self) + + self.trayIconMenu.addAction(self.connectVPNAction) + self.trayIconMenu.addAction(self.dis_connectAction) + self.trayIconMenu.addSeparator() + self.trayIconMenu.addAction(self.minimizeAction) + self.trayIconMenu.addAction(self.maximizeAction) + self.trayIconMenu.addAction(self.restoreAction) + self.trayIconMenu.addSeparator() + self.trayIconMenu.addAction(self.quitAction) + + self.trayIcon = QSystemTrayIcon(self) + self.trayIcon.setContextMenu(self.trayIconMenu) + + def createLogBrowser(self): + """ + creates Browser widget for displaying logs + (in debug mode only). + """ + self.loggerBox = QGroupBox() + logging_layout = QVBoxLayout() + self.logbrowser = QTextBrowser() + + startStopButton = QPushButton("&Connect") + startStopButton.clicked.connect(self.start_or_stopVPN) + self.startStopButton = startStopButton + + logging_layout.addWidget(self.logbrowser) + logging_layout.addWidget(self.startStopButton) + self.loggerBox.setLayout(logging_layout) + + # status box + + self.statusBox = QGroupBox() + grid = QGridLayout() + + self.updateTS = QLabel('') + self.status_label = QLabel('Disconnected') + self.ip_label = QLabel('') + self.remote_label = QLabel('') + + tun_read_label = QLabel("tun read") + self.tun_read_bytes = QLabel("0") + tun_write_label = QLabel("tun write") + self.tun_write_bytes = QLabel("0") + + grid.addWidget(self.updateTS, 0, 0) + grid.addWidget(self.status_label, 0, 1) + grid.addWidget(self.ip_label, 1, 0) + grid.addWidget(self.remote_label, 1, 1) + grid.addWidget(tun_read_label, 2, 0) + grid.addWidget(self.tun_read_bytes, 2, 1) + grid.addWidget(tun_write_label, 3, 0) + grid.addWidget(self.tun_write_bytes, 3, 1) + + self.statusBox.setLayout(grid) + + @pyqtSlot(str) + def onLoggerNewLine(self, line): + """ + simple slot: writes new line to logger Pane. + """ + if self.debugmode: + self.logbrowser.append(line[:-1]) + + def set_statusbarMessage(self, msg): + self.statusBar().showMessage(msg) + + @pyqtSlot(object) + def onStatusChange(self, status): + """ + slot for status changes. triggers new signals for + updating icon, status bar, etc. + """ + + print('STATUS CHANGED! (on Qt-land)') + print('%s -> %s' % (status.previous, status.current)) + icon_name = self.conductor.get_icon_name() + self.setIcon(icon_name) + print 'icon = ', icon_name + + # change connection pixmap widget + self.setConnWidget(icon_name) + + def setConnWidget(self, icon_name): + #print 'changing icon to %s' % icon_name + oldlayout = self.statusIconBox.layout() + + # XXX reuse with icons + # XXX move states to StateWidget + states = {"disconnected": 0, + "connecting": 1, + "connected": 2} + + for i in range(3): + oldlayout.itemAt(i).widget().hide() + new = states[icon_name] + oldlayout.itemAt(new).widget().show() + + @pyqtSlot() + def start_or_stopVPN(self): + """ + stub for running child process with vpn + """ + if self.vpn_service_started is False: + self.conductor.connect() + if self.debugmode: + self.startStopButton.setText('&Disconnect') + self.vpn_service_started = True + + # XXX what is optimum polling interval? + # too little is overkill, too much + # will miss transition states.. + + self.timer.start(250.0) + return + if self.vpn_service_started is True: + self.conductor.disconnect() + # FIXME this should trigger also + # statuschange event. why isn't working?? + if self.debugmode: + self.startStopButton.setText('&Connect') + self.vpn_service_started = False + self.timer.stop() + return + + @pyqtSlot() + def onTimerTick(self): + self.statusUpdate() + + @pyqtSlot() + def statusUpdate(self): + """ + called on timer tick + polls status and updates ui with real time + info about transferred bytes / connection state. + """ + # XXX it's too expensive to poll + # continously. move to signal events instead. + + if not self.vpn_service_started: + return + + # XXX remove all access to manager layer + # from here. + if self.conductor.manager.with_errors: + #XXX how to wait on pkexec??? + #something better that this workaround, plz!! + time.sleep(10) + print('errors. disconnect.') + self.start_or_stopVPN() # is stop + + state = self.conductor.poll_connection_state() + if not state: + return + + ts, con_status, ok, ip, remote = state + self.set_statusbarMessage(con_status) + self.setToolTip() + + ts = time.strftime("%a %b %d %X", ts) + if self.debugmode: + self.updateTS.setText(ts) + self.status_label.setText(con_status) + self.ip_label.setText(ip) + self.remote_label.setText(remote) + + # status i/o + + status = self.conductor.manager.get_status_io() + if status and self.debugmode: + #XXX move this to systray menu indicators + ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read) = status + ts = time.strftime("%a %b %d %X", ts) + self.updateTS.setText(ts) + self.tun_read_bytes.setText(tun_read) + self.tun_write_bytes.setText(tun_write) + + def cleanupAndQuit(self): + """ + cleans state before shutting down app. + """ + # TODO:make sure to shutdown all child process / threads + # in conductor + self.conductor.cleanup() + qApp.quit() diff --git a/src/leap/eip/__init__.py b/src/leap/eip/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/eip/__init__.py diff --git a/src/leap/eip/conductor.py b/src/leap/eip/conductor.py new file mode 100644 index 00000000..e3adadc4 --- /dev/null +++ b/src/leap/eip/conductor.py @@ -0,0 +1,272 @@ +""" +stablishes a vpn connection and monitors its state +""" +from __future__ import (division, unicode_literals, print_function) +#import threading +from functools import partial +import logging + +from leap.utils.coroutines import spawn_and_watch_process +from leap.baseapp.config import get_config, get_vpn_stdout_mockup +from leap.eip.vpnwatcher import EIPConnectionStatus, status_watcher +from leap.eip.vpnmanager import OpenVPNManager, ConnectionRefusedError + +logger = logging.getLogger(name=__name__) + + +# TODO Move exceptions to their own module + + +class ConnectionError(Exception): + """ + generic connection error + """ + pass + + +class EIPClientError(Exception): + """ + base EIPClient exception + """ + def __str__(self): + if len(self.args) >= 1: + return repr(self.args[0]) + else: + return ConnectionError + + +class UnrecoverableError(EIPClientError): + """ + we cannot do anything about it, sorry + """ + pass + + +class OpenVPNConnection(object): + """ + All related to invocation + of the openvpn binary + """ + # Connection Methods + + def __init__(self, config_file=None, watcher_cb=None): + #XXX FIXME + #change watcher_cb to line_observer + """ + :param config_file: configuration file to read from + :param watcher_cb: callback to be \ +called for each line in watched stdout + :param signal_map: dictionary of signal names and callables \ +to be triggered for each one of them. + :type config_file: str + :type watcher_cb: function + :type signal_map: dict + """ + # XXX get host/port from config + self.manager = OpenVPNManager() + + self.config_file = config_file + self.watcher_cb = watcher_cb + #self.signal_maps = signal_maps + + self.subp = None + self.watcher = None + + self.server = None + self.port = None + self.proto = None + + self.autostart = True + + self._get_config() + + def _set_command_mockup(self): + """ + sets command and args for a command mockup + that just mimics the output from the real thing + """ + command, args = get_vpn_stdout_mockup() + self.command, self.args = command, args + + def _get_config(self): + """ + retrieves the config options from defaults or + home file, or config file passed in command line. + """ + config = get_config(config_file=self.config_file) + self.config = config + + if config.has_option('openvpn', 'command'): + commandline = config.get('openvpn', 'command') + if commandline == "mockup": + self._set_command_mockup() + return + command_split = commandline.split(' ') + command = command_split[0] + if len(command_split) > 1: + args = command_split[1:] + else: + args = [] + self.command = command + #print("debug: command = %s" % command) + self.args = args + else: + self._set_command_mockup() + + if config.has_option('openvpn', 'autostart'): + autostart = config.get('openvpn', 'autostart') + self.autostart = autostart + + def _launch_openvpn(self): + """ + invocation of openvpn binaries in a subprocess. + """ + #XXX TODO: + #deprecate watcher_cb, + #use _only_ signal_maps instead + + if self.watcher_cb is not None: + linewrite_callback = self.watcher_cb + else: + #XXX get logger instead + linewrite_callback = lambda line: print('watcher: %s' % line) + + observers = (linewrite_callback, + partial(status_watcher, self.status)) + subp, watcher = spawn_and_watch_process( + self.command, + self.args, + observers=observers) + self.subp = subp + self.watcher = watcher + + conn_result = self.status.CONNECTED + return conn_result + + def _try_connection(self): + """ + attempts to connect + """ + if self.subp is not None: + print('cowardly refusing to launch subprocess again') + return + self._launch_openvpn() + + def cleanup(self): + """ + terminates child subprocess + """ + if self.subp: + self.subp.terminate() + + +class EIPConductor(OpenVPNConnection): + """ + Manages the execution of the OpenVPN process, auto starts, monitors the + network connection, handles configuration, fixes leaky hosts, handles + errors, etc. + Preferences will be stored via the Storage API. (TBD) + Status updates (connected, bandwidth, etc) are signaled to the GUI. + """ + + def __init__(self, *args, **kwargs): + self.settingsfile = kwargs.get('settingsfile', None) + self.logfile = kwargs.get('logfile', None) + self.error_queue = [] + self.desired_con_state = None # ??? + + status_signals = kwargs.pop('status_signals', None) + self.status = EIPConnectionStatus(callbacks=status_signals) + + super(EIPConductor, self).__init__(*args, **kwargs) + + def connect(self): + """ + entry point for connection process + """ + self.manager.forget_errors() + self._try_connection() + # XXX should capture errors? + + def disconnect(self): + """ + disconnects client + """ + self._disconnect() + self.status.change_to(self.status.DISCONNECTED) + pass + + def shutdown(self): + """ + shutdown and quit + """ + self.desired_con_state = self.status.DISCONNECTED + + def connection_state(self): + """ + returns the current connection state + """ + return self.status.current + + def desired_connection_state(self): + """ + returns the desired_connection state + """ + return self.desired_con_state + + def poll_connection_state(self): + """ + """ + try: + state = self.manager.get_connection_state() + except ConnectionRefusedError: + # connection refused. might be not ready yet. + return + if not state: + return + (ts, status_step, + ok, ip, remote) = state + self.status.set_vpn_state(status_step) + status_step = self.status.get_readable_status() + return (ts, status_step, ok, ip, remote) + + def get_icon_name(self): + """ + get icon name from status object + """ + return self.status.get_state_icon() + + # + # private methods + # + + def _disconnect(self): + """ + private method for disconnecting + """ + if self.subp is not None: + self.subp.terminate() + self.subp = None + # XXX signal state changes! :) + + def _is_alive(self): + """ + don't know yet + """ + pass + + def _connect(self): + """ + entry point for connection cascade methods. + """ + #conn_result = ConState.DISCONNECTED + try: + conn_result = self._try_connection() + except UnrecoverableError as except_msg: + logger.error("FATAL: %s" % unicode(except_msg)) + conn_result = self.status.UNRECOVERABLE + except Exception as except_msg: + self.error_queue.append(except_msg) + logger.error("Failed Connection: %s" % + unicode(except_msg)) + return conn_result diff --git a/src/leap/eip/vpnmanager.py b/src/leap/eip/vpnmanager.py new file mode 100644 index 00000000..78777cfb --- /dev/null +++ b/src/leap/eip/vpnmanager.py @@ -0,0 +1,262 @@ +from __future__ import (print_function) +import logging +import os +import socket +import telnetlib +import time + +logger = logging.getLogger(name=__name__) + +TELNET_PORT = 23 + + +class MissingSocketError(Exception): + pass + + +class ConnectionRefusedError(Exception): + pass + + +class UDSTelnet(telnetlib.Telnet): + + def open(self, host, port=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + """Connect to a host. If port is 'unix', it + will open a connection over unix docmain sockets. + + The optional second argument is the port number, which + defaults to the standard telnet port (23). + + Don't try to reopen an already connected instance. + """ + self.eof = 0 + if not port: + port = TELNET_PORT + self.host = host + self.port = port + self.timeout = timeout + + if self.port == "unix": + # unix sockets spoken + if not os.path.exists(self.host): + raise MissingSocketError + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + self.sock.connect(self.host) + except socket.error: + raise ConnectionRefusedError + else: + self.sock = socket.create_connection((host, port), timeout) + + +# this class based in code from cube-routed project + +class OpenVPNManager(object): + """ + Run commands over OpenVPN management interface + and parses the output. + """ + # XXX might need a lock to avoid + # race conditions here... + + def __init__(self, host="/tmp/.eip.sock", port="unix", password=None): + #XXX hardcoded host here. change. + self.host = host + if isinstance(port, str) and port.isdigit(): + port = int(port) + self.port = port + self.password = password + self.tn = None + + #XXX workaround for signaling + #the ui that we don't know how to + #manage a connection error + self.with_errors = False + + def forget_errors(self): + print('forgetting errors') + self.with_errors = False + + def connect(self): + """Connect to openvpn management interface""" + try: + self.close() + except: + #XXX don't like this general + #catch here. + pass + if self.connected(): + return True + self.tn = UDSTelnet(self.host, self.port) + + # XXX make password optional + # specially for win plat. we should generate + # the pass on the fly when invoking manager + # from conductor + + #self.tn.read_until('ENTER PASSWORD:', 2) + #self.tn.write(self.password + '\n') + #self.tn.read_until('SUCCESS:', 2) + + self._seek_to_eof() + self.forget_errors() + return True + + def _seek_to_eof(self): + """ + Read as much as available. Position seek pointer to end of stream + """ + b = self.tn.read_eager() + while b: + b = self.tn.read_eager() + + def connected(self): + """ + Returns True if connected + rtype: bool + """ + #return bool(getattr(self, 'tn', None)) + try: + assert self.tn + return True + except: + #XXX get rid of + #this pokemon exception!!! + return False + + def close(self, announce=True): + """ + Close connection to openvpn management interface + """ + if announce: + self.tn.write("quit\n") + self.tn.read_all() + self.tn.get_socket().close() + del self.tn + + def _send_command(self, cmd, tries=0): + """ + Send a command to openvpn and return response as list + """ + if tries > 3: + return [] + if not self.connected(): + try: + self.connect() + except MissingSocketError: + #XXX capture more helpful error + #messages + #pass + return self.make_error() + try: + self.tn.write(cmd + "\n") + except socket.error: + logger.error('socket error') + print('socket error!') + self.close(announce=False) + self._send_command(cmd, tries=tries + 1) + return [] + buf = self.tn.read_until(b"END", 2) + self._seek_to_eof() + blist = buf.split('\r\n') + if blist[-1].startswith('END'): + del blist[-1] + return blist + else: + return [] + + def _send_short_command(self, cmd): + """ + parse output from commands that are + delimited by "success" instead + """ + if not self.connected(): + self.connect() + self.tn.write(cmd + "\n") + # XXX not working? + buf = self.tn.read_until(b"SUCCESS", 2) + self._seek_to_eof() + blist = buf.split('\r\n') + return blist + + # + # useful vpn commands + # + + def pid(self): + #XXX broken + return self._send_short_command("pid") + + def make_error(self): + """ + capture error and wrap it in an + understandable format + """ + #XXX get helpful error codes + self.with_errors = True + now = int(time.time()) + return '%s,LAUNCHER ERROR,ERROR,-,-' % now + + def state(self): + """ + OpenVPN command: state + """ + state = self._send_command("state") + if not state: + return None + if isinstance(state, str): + return state + if isinstance(state, list): + if len(state) == 1: + return state[0] + else: + return state[-1] + + def status(self): + """ + OpenVPN command: status + """ + status = self._send_command("status") + return status + + def status2(self): + """ + OpenVPN command: last 2 statuses + """ + return self._send_command("status 2") + + # + # parse info + # + + def get_status_io(self): + status = self.status() + if isinstance(status, str): + lines = status.split('\n') + if isinstance(status, list): + lines = status + try: + (header, when, tun_read, tun_write, + tcp_read, tcp_write, auth_read) = tuple(lines) + except ValueError: + return None + + when_ts = time.strptime(when.split(',')[1], "%a %b %d %H:%M:%S %Y") + sep = ',' + # XXX cleanup! + tun_read = tun_read.split(sep)[1] + tun_write = tun_write.split(sep)[1] + tcp_read = tcp_read.split(sep)[1] + tcp_write = tcp_write.split(sep)[1] + auth_read = auth_read.split(sep)[1] + + # XXX this could be a named tuple. prettier. + return when_ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read) + + def get_connection_state(self): + state = self.state() + if state is not None: + ts, status_step, ok, ip, remote = state.split(',') + ts = time.gmtime(float(ts)) + # XXX this could be a named tuple. prettier. + return ts, status_step, ok, ip, remote diff --git a/src/leap/eip/vpnwatcher.py b/src/leap/eip/vpnwatcher.py new file mode 100644 index 00000000..09bd5811 --- /dev/null +++ b/src/leap/eip/vpnwatcher.py @@ -0,0 +1,169 @@ +"""generic watcher object that keeps track of connection status""" +# This should be deprecated in favor of daemon mode + management +# interface. But we can leave it here for debug purposes. + + +class EIPConnectionStatus(object): + """ + Keep track of client (gui) and openvpn + states. + + These are the OpenVPN states: + CONNECTING -- OpenVPN's initial state. + WAIT -- (Client only) Waiting for initial response + from server. + AUTH -- (Client only) Authenticating with server. + GET_CONFIG -- (Client only) Downloading configuration options + from server. + ASSIGN_IP -- Assigning IP address to virtual network + interface. + ADD_ROUTES -- Adding routes to system. + CONNECTED -- Initialization Sequence Completed. + RECONNECTING -- A restart has occurred. + EXITING -- A graceful exit is in progress. + + We add some extra states: + + DISCONNECTED -- GUI initial state. + UNRECOVERABLE -- An unrecoverable error has been raised + while invoking openvpn service. + """ + CONNECTING = 1 + WAIT = 2 + AUTH = 3 + GET_CONFIG = 4 + ASSIGN_IP = 5 + ADD_ROUTES = 6 + CONNECTED = 7 + RECONNECTING = 8 + EXITING = 9 + + # gui specific states: + UNRECOVERABLE = 11 + DISCONNECTED = 0 + + def __init__(self, callbacks=None): + """ + EIPConnectionStatus is initialized with a tuple + of signals to be triggered. + :param callbacks: a tuple of (callable) observers + :type callbacks: tuple + """ + # (callbacks to connect to signals in Qt-land) + self.current = self.DISCONNECTED + self.previous = None + self.callbacks = callbacks + + def get_readable_status(self): + # XXX DRY status / labels a little bit. + # think we'll want to i18n this. + human_status = { + 0: 'disconnected', + 1: 'connecting', + 2: 'waiting', + 3: 'authenticating', + 4: 'getting config', + 5: 'assigning ip', + 6: 'adding routes', + 7: 'connected', + 8: 'reconnecting', + 9: 'exiting', + 11: 'unrecoverable error', + } + return human_status[self.current] + + def get_state_icon(self): + """ + returns the high level icon + for each fine-grain openvpn state + """ + connecting = (self.CONNECTING, + self.WAIT, + self.AUTH, + self.GET_CONFIG, + self.ASSIGN_IP, + self.ADD_ROUTES) + connected = (self.CONNECTED,) + disconnected = (self.DISCONNECTED, + self.UNRECOVERABLE) + + # this can be made smarter, + # but it's like it'll change, + # so +readability. + + if self.current in connecting: + return "connecting" + if self.current in connected: + return "connected" + if self.current in disconnected: + return "disconnected" + + def set_vpn_state(self, status): + """ + accepts a state string from the management + interface, and sets the internal state. + :param status: openvpn STATE (uppercase). + :type status: str + """ + if hasattr(self, status): + self.change_to(getattr(self, status)) + + def set_current(self, to): + """ + setter for the 'current' property + :param to: destination state + :type to: int + """ + self.current = to + + def change_to(self, to): + """ + :param to: destination state + :type to: int + """ + if to == self.current: + return + changed = False + from_ = self.current + self.current = to + + # We can add transition restrictions + # here to ensure no transitions are + # allowed outside the fsm. + + self.set_current(to) + changed = True + + #trigger signals (as callbacks) + #print('current state: %s' % self.current) + if changed: + self.previous = from_ + if self.callbacks: + for cb in self.callbacks: + if callable(cb): + cb(self) + + +def status_watcher(cs, line): + """ + a wrapper that calls to ConnectionStatus object + :param cs: a EIPConnectionStatus instance + :type cs: EIPConnectionStatus object + :param line: a single line of the watched output + :type line: str + """ + #print('status watcher watching') + + # from the mullvad code, should watch for + # things like: + # "Initialization Sequence Completed" + # "With Errors" + # "Tap-Win32" + + if "Completed" in line: + cs.change_to(cs.CONNECTED) + return + + if "Initial packet from" in line: + cs.change_to(cs.CONNECTING) + return diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/gui/__init__.py diff --git a/src/leap/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py new file mode 100644 index 00000000..e5a671f3 --- /dev/null +++ b/src/leap/gui/mainwindow_rc.py @@ -0,0 +1,789 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created: Sun Jul 22 17:08:49 2012 +# by: The Resource Compiler for PyQt (Qt v4.8.2) +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore + +qt_resource_data = "\ +\x00\x00\x0d\xf3\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ +\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0e\x74\x45\ +\x58\x74\x54\x69\x74\x6c\x65\x00\x43\x6f\x6d\x70\x75\x74\x65\x72\ +\xf8\x18\x12\x76\x00\x00\x00\x17\x74\x45\x58\x74\x41\x75\x74\x68\ +\x6f\x72\x00\x4c\x61\x70\x6f\x20\x43\x61\x6c\x61\x6d\x61\x6e\x64\ +\x72\x65\x69\xdf\x91\x1a\x2a\x00\x00\x0d\x33\x49\x44\x41\x54\x68\ +\xde\xdd\x9a\x7b\x8c\x5d\xc5\x7d\xc7\x3f\xbf\x99\x39\xe7\x3e\xf6\ +\xe9\x5d\x7b\x8d\x6d\xd6\x6b\x9b\xda\x98\xd2\x42\x03\xf1\xda\x4b\ +\x83\x09\x46\x94\x04\xa8\x54\x29\x75\x84\x92\x86\x54\x04\x35\x81\ +\x4a\x50\x55\xb4\x85\xb4\x55\x4b\xd3\xfe\x55\x12\x25\x0a\x6d\x95\ +\x84\x14\xa9\x4a\x68\x28\x49\x09\x34\x60\x4a\x52\xe1\x40\xa1\x60\ +\x43\x40\x25\x80\xcd\xc3\xac\xf1\xe2\xc7\xae\xf7\xe1\xdd\xbd\xf7\ +\xee\xbd\xe7\xcc\xfc\xfa\xc7\x39\xf7\xee\xae\x03\xc6\x3c\x54\x4a\ +\x8f\x34\x3b\x73\xe7\xcc\x39\xe7\xf7\xfc\x7e\x7f\x33\x5a\x51\x55\ +\x3e\xc8\x97\xf9\x40\x4b\xff\xff\x41\x01\xf7\x6e\x1e\xde\xf1\xe3\ +\x7b\xbf\x8e\xca\xef\xa3\x48\xd0\x40\x08\x81\x10\x14\x5d\x38\xf6\ +\x81\x40\xc0\x7b\x45\x43\x20\xa8\xcf\xe6\x43\xbe\x46\x15\xef\x7d\ +\xfe\x5b\x09\x1a\x08\xde\xb7\xc6\xd9\xbd\x7c\x3e\x04\x34\x28\xde\ +\x87\x20\x12\xfe\xe8\xcf\xff\xf4\xaf\xbe\xf2\x8e\x15\xd8\xb1\x63\ +\x47\x27\x86\xcf\xff\xfa\x79\x5b\xc5\x18\x83\x88\x41\x44\x10\x04\ +\x11\x20\x1f\x83\xa2\x9a\xb5\x4c\xe0\x5c\xb1\xe0\xf1\x4d\x25\x82\ +\xc7\xfb\xac\x4f\xbd\x27\xf8\x94\xd4\x7b\x7c\x9a\xf7\xf9\x6f\x0d\ +\x01\x55\xa5\x56\xab\x99\x87\x76\x3e\xf4\x25\xe0\x9d\x2b\x50\xaf\ +\xd7\x4d\xa1\x6c\x43\xa5\x5a\xe7\xcc\xcb\xfe\x84\xf0\xae\xb1\x40\ +\xd1\xac\x3b\xe1\xd5\x5e\x72\xdc\xfd\xb5\xcf\x11\xbc\x9a\x77\x1d\ +\x42\xad\x30\x41\xe9\x3b\x6d\x10\x05\x34\x97\x42\x95\xcc\xf2\xcd\ +\xb9\x05\xe3\x81\x15\x5d\xa0\xe0\xbd\x27\x0d\x4a\x23\xf1\x8c\x4e\ +\x56\x50\x85\xb6\x52\xcc\x86\xd5\x3d\x84\xdc\x22\x59\x28\x65\x2d\ +\x49\x12\xfe\x7b\xd7\x4f\x89\xa2\x88\xa0\x81\xf7\x46\x01\x55\xc0\ +\xa0\x62\x17\x59\x52\xf3\x91\x1e\x37\xe7\x9c\xc1\x5a\x47\xea\x03\ +\x2a\xa0\xa2\x54\xea\x09\x01\x8b\x0a\x2c\xef\xed\x20\x04\x21\xe4\ +\xca\x86\x3c\x0c\xb3\x96\x61\x4e\x14\xc5\x84\xf0\x9e\x28\x90\xc5\ +\x32\x2c\xb6\x76\x73\x8c\xe6\x9e\x68\xde\x57\x88\x8b\x86\xd4\x67\ +\x9e\x4b\x43\x20\xf5\xca\x6c\x2d\x41\x55\x29\xc6\x8e\x38\xb2\xcc\ +\x25\x1e\x14\x42\xee\x49\x50\x42\x96\xbc\x00\xc4\xef\x8d\x02\x53\ +\xa4\x69\x1b\x8d\x24\x69\x7d\x60\x5e\x89\x4c\xea\x85\x82\x6b\x3e\ +\x11\x1b\x93\x85\x8e\xcf\x14\xa8\xd4\x1a\xa4\x3e\x00\x4a\x4f\x67\ +\x91\x7a\xc3\x67\x9e\xd3\xf9\x7c\x08\x9a\xf9\xd2\xa7\x3e\xf7\x40\ +\xf4\xde\x78\xc0\xfb\x94\xa4\x91\x80\x2a\xde\xa7\x2d\x21\xe7\x3d\ +\xc1\x22\x61\x14\xc5\x18\xa5\x91\x24\x78\x0f\x5e\x61\xb6\x5a\x47\ +\x83\x12\xc7\x16\x6b\x0d\x8d\x34\x83\xcd\xd6\x3b\xf2\xe7\x83\x2a\ +\xea\xdf\x43\x05\xa6\x00\x5b\xaf\x33\x33\x3b\x8d\xa6\x09\x3e\x99\ +\xcb\x43\x69\xfe\xc3\x20\xad\xf5\x39\xb2\x22\xa4\xf8\x44\x09\x01\ +\xea\x69\x20\x4d\x53\x44\x2c\x9d\xe5\x02\x49\xea\x51\x95\x96\xc5\ +\x51\x5a\xc9\xac\x79\xd2\x03\x38\xe7\x5a\xa1\xeb\xde\x45\x04\x51\ +\x49\x66\x65\x6c\x6c\x14\x4d\xe7\x08\x8d\xd9\x5c\x50\x83\x08\x98\ +\x8c\x0c\xe6\xa5\x07\x22\x6b\xd1\x7a\x05\x9f\x27\x67\x52\xf7\x44\ +\x62\x70\xb1\xa5\x18\x3b\x14\xc9\xd7\x4a\x06\x10\x3e\x27\x2e\x0d\ +\xf8\x30\xef\x01\x6b\x1d\xbe\xe9\x81\x77\xca\xa6\xe2\x94\xa2\x29\ +\x72\xe4\xf0\x01\xbe\x7c\xc3\x6f\xbc\x0d\x36\xad\xa1\x41\x49\x7d\ +\x60\xf7\xb0\xf2\xec\x91\x12\xed\xc5\x32\x18\x45\xb1\xf3\x80\x93\ +\x2b\xae\xa2\xad\x3c\xca\x53\x00\x6b\x0c\xc1\x6b\xee\x81\xf0\xbe\ +\xb1\x29\x89\x7f\x88\xbd\xa3\x1e\x67\x00\x35\x18\x63\x50\x11\x24\ +\xf3\x05\x06\x21\x10\xf2\x9c\x50\x54\x9a\x1e\x15\xb4\xc5\x03\x22\ +\xef\x1b\x9b\xaa\x57\x22\x97\x3d\x20\xc6\x20\x62\x09\x22\xa0\x8a\ +\x48\x2e\x7c\x33\x04\x8d\x6f\xc5\x62\x66\xa8\x5c\x81\xf7\x9b\x4d\ +\x0d\x20\x22\x18\x31\xa8\xc9\xc8\x50\x0c\xa0\x92\x59\xbc\x69\x11\ +\x35\xb9\x4f\xc0\x87\xf4\x38\x05\xde\x47\x36\xb5\x51\x84\x73\x16\ +\xe3\x2c\x18\x47\x40\x5a\x84\xd5\x72\x64\x00\x15\xc1\x64\xe5\x0f\ +\x69\xb2\x48\x81\xf7\x8f\x4d\x55\x03\x91\xb3\xb8\x28\xc2\xb9\x18\ +\xb5\x0e\xaf\x64\xb9\x68\x04\x31\x01\x11\x8f\x78\x01\x3c\x84\xcc\ +\xc0\x49\x92\xcc\x2b\xe0\x7d\x20\xcd\xe1\x29\x2c\x0a\x99\x13\xb0\ +\xa9\xb5\x64\xcf\x29\x69\xd0\x77\xcc\xa6\x69\x9a\xb2\xf7\xc9\xff\ +\xc0\x44\x65\x24\x2a\x22\xc6\xe5\xf1\xd3\x0c\x04\x5d\x14\xd2\x22\ +\x59\x6e\x24\x69\xa3\x15\x9e\x2e\x04\x4f\xc8\xad\xd2\xcc\x85\x37\ +\x12\x7c\x21\x9b\x5a\x6b\x48\x7c\x06\xab\x3e\x28\xb3\xb5\x06\xaa\ +\x4a\x1c\xbd\x3d\x36\x35\x02\x5f\xbb\xe9\x93\xf4\xf4\xf6\xd0\xd5\ +\xd5\x4d\xa1\x10\x63\x8c\x69\xc1\xb0\x0f\x01\xef\x43\x8e\x76\x59\ +\x23\xcf\xa3\x45\x39\x90\xdd\x20\xcf\x85\xe3\x2c\x9e\x71\x68\xc6\ +\xa2\x22\x20\x8a\x8b\x6c\x0b\x87\x1b\xde\xe3\x43\xe6\xf6\xce\xb6\ +\x02\x49\x1a\xe6\x85\x7d\x0b\x36\x45\x95\xae\x8e\x76\x7a\xba\x3b\ +\x59\xb2\xa4\x8b\x52\xa9\x84\x31\x19\x44\xfa\x90\x41\x76\x16\xc2\ +\x81\xe7\x9e\x7f\x81\x27\x9f\x7c\x92\x6d\xdb\xb6\x51\xab\x56\x09\ +\xf9\x4b\x5d\x86\xcf\xc9\x3c\xe5\x0b\x18\xb1\xd0\x14\x98\x79\xec\ +\x05\x25\x8e\x1c\xd6\xd8\x2c\xa9\x45\xf1\xf5\x94\x38\x8a\xb1\x56\ +\x28\x15\xe2\xdc\x08\x7a\x52\x6c\x9a\xa6\x29\xf7\xdc\x73\x37\xe5\ +\xf6\x36\x4a\xe5\x12\x91\x73\xb5\x8c\x6b\x32\xd2\x5b\x48\x8c\xd5\ +\x4a\x25\x2e\x95\xca\x72\xe7\xf7\xfe\x39\x20\x12\x50\xfe\x2e\x53\ +\x20\x28\x3e\x4d\x5b\x6e\xcd\xea\x15\x03\x86\x79\x52\x23\xb3\x3c\ +\x08\xc5\x42\x84\xb1\x36\x77\x4c\x40\x8c\xa5\x50\x30\xb4\x15\x23\ +\xc4\x66\x88\x91\x27\xd3\x5b\xb2\xa9\x2a\x9c\xf5\x6b\x67\xd1\xb7\ +\xfc\x14\xf6\xef\x1f\xae\x3f\xff\xec\x9e\xd3\xcb\xe5\xb2\x2f\x16\ +\x8b\x01\xaa\x54\x81\xec\x0f\x80\xa1\x3a\x3b\x07\xd4\xa8\xd7\x5d\ +\x7a\xc7\x1d\x77\x1c\x05\x70\x1a\xb2\x24\x56\x20\x72\x76\x9e\x8d\ +\x8d\xb4\x14\x40\x72\x66\x56\xa1\x54\x88\x30\x56\x10\x03\x69\xc3\ +\x13\x45\x31\x56\x20\x8e\x1d\x21\x28\xc6\x64\xf4\x23\x39\x78\x9e\ +\x88\x4d\x41\xf9\xd5\x33\xcf\x66\xf5\xea\x01\xfa\x96\xf5\x6a\x14\ +\xb9\xf3\x6e\xfa\xe3\x3f\xbb\x0f\x98\x03\xbc\x9e\xc4\xa1\x95\xcb\ +\xe2\x2c\x7b\x9f\x73\x0e\x63\x72\x08\x93\x8c\x34\xc4\x98\xbc\x30\ +\xcb\x3c\x12\xc7\x31\x82\x66\xac\x6d\xa1\x10\x0b\xce\x99\xfc\x1d\ +\x8a\xf1\x82\x1a\x05\x95\xb7\x64\x53\xef\x3d\x2f\xbf\xfc\x0a\x95\ +\x6a\x95\xb6\xf6\xb6\xe2\xca\x15\x2b\xbf\xfb\x8d\x6f\xfe\xfd\x70\ +\xb1\x58\x78\xa1\xab\xa7\xdb\x3d\xf0\xe0\x8f\xda\x8c\x31\x91\x08\ +\x36\x64\x85\x67\x8a\x6a\x25\x68\x78\x58\x93\xb9\x7f\xb8\xf4\xd2\ +\xed\x87\x5d\x08\xa1\xe5\x6d\x17\x39\x8c\x64\x02\x8b\xcd\x7b\x31\ +\x18\x93\x07\x92\x11\x5c\xe4\x5a\x21\x12\x47\x92\xbd\x36\x67\x59\ +\x54\xf1\x6a\x20\x78\xc4\xc8\x49\xb1\x69\x5c\xb4\x88\xf1\xc4\x05\ +\xcb\xe0\xe0\xa0\x0d\x9e\x75\xdf\xfe\xc7\xdb\xa2\xa0\x7c\x61\xf5\ +\xaa\x15\xe3\xcb\x96\x2d\x4b\xa3\x28\xb6\x59\xd4\x19\x67\x62\xbb\ +\xa4\x5c\x2a\x5d\x61\xe3\xf2\x1d\xc0\xb6\x4c\x81\x1c\xac\xad\xb1\ +\x18\x63\xb0\xd6\xe4\x82\x1b\x6c\xd3\x1b\x26\x53\xc4\x5a\x03\x16\ +\x08\x81\xa2\xc9\x43\x3e\xaf\x5e\xbd\x0f\x88\x51\xc4\x9b\x16\xda\ +\x9c\x88\x4d\xd3\x34\xe5\x87\x3f\xbc\x27\x8d\xa2\x48\xad\xcd\xbe\ +\x0d\x62\x66\x66\x66\x76\xef\x7a\x7c\xf7\x81\xa1\xa1\xa1\x99\xab\ +\xbf\xf0\xd9\xbf\x70\x36\xfe\xa8\x20\x25\xd0\xa2\x0a\x3e\x84\x10\ +\x19\x23\x3f\x12\x11\x71\x4d\xa8\xca\x90\xc1\x60\x16\x28\x61\x8d\ +\xc1\x58\x83\xc9\x15\x70\x56\xf2\xd8\x15\xac\x4a\x6e\xd8\x0c\x5d\ +\x7c\x08\x58\xa3\xa4\xc1\x67\xec\xd9\xcc\xa3\x13\xb3\x69\xe3\xc5\ +\x3d\x2f\x0f\x01\x9c\x61\x4c\xf9\x0f\x55\x2f\x59\x11\xc2\x87\xdb\ +\xd2\x74\x8b\xed\xe8\xf8\x59\xfd\x95\x97\x67\xa6\x6f\xfd\x56\x2d\ +\x99\xa9\x7c\xf3\x89\x8b\x2e\xba\x6b\xbf\x6b\xcc\x74\xba\xc8\x2e\ +\x59\xd2\x55\xdf\xb9\x73\xf7\x58\x2b\x89\x51\xa5\x5e\xaf\xf3\xd2\ +\xee\x07\x10\xe6\xd1\x63\x61\x55\x79\x7c\x36\xc9\x9b\xcc\xff\x62\ +\x81\xfa\xe6\x6c\xaa\x4a\xbc\x69\xcb\xe0\x53\x9b\x9c\x65\xfb\x53\ +\xcf\x68\xb4\xa4\x4b\x4c\x77\x17\x2c\xed\x45\x3a\x3b\x30\x93\x53\ +\x3d\x85\x23\xa3\xd4\xaa\x95\x9b\xcf\xfb\xce\x77\xfe\x32\xbd\xe8\ +\xa3\xbb\x1e\x38\x78\xe4\x82\x1d\x3b\x76\xa4\x19\xb9\xab\xba\x10\ +\x02\xe5\x72\xc4\x03\xdf\xb8\x86\xa6\x1b\xb3\x50\xca\xc6\x0b\x9b\ +\x48\x13\x99\x9a\x4c\x9d\xe3\x74\xbe\x1f\xc8\xc6\x9e\x26\x39\x9e\ +\x0c\x9b\xfe\xf6\xe8\x28\x43\x87\x8e\x10\xad\xe9\x17\x2d\x95\xd0\ +\xc3\x47\x08\x07\x5e\x47\x2b\x55\x4c\x7b\x1b\xa6\xbd\x8d\xf6\x8d\ +\x1b\x28\x8e\x1d\x95\x8f\xdd\xf7\xc0\xa6\xb5\xa7\xae\xbc\xed\x7e\ +\xd5\xcf\xb4\x50\x28\x4d\x52\xa3\xaa\xf4\xf5\xf6\x64\xe1\x62\x04\ +\x2b\x16\xb1\x16\x23\xd2\x12\xfc\x8d\x15\x68\x6e\x72\xe6\x77\x70\ +\xd9\x6f\x9f\x6d\x70\xde\x82\x4d\xd7\x4e\x4c\xb2\xe9\xb5\x11\xdc\ +\xc6\xf5\xe8\xc4\x24\xe1\xb9\x3d\x39\xf4\xe6\xb5\xd8\xc4\x24\x3a\ +\x3e\x41\x78\x6d\x04\x33\xd0\x4f\xc7\x87\xce\x32\x6b\x77\x3d\xf5\ +\xe9\x87\xfb\xfa\xbe\xb2\x75\x74\xf4\x69\x00\xf9\xfa\xad\x5f\xfd\ +\xbe\xd7\x70\x39\x68\x60\x11\x0b\x2e\x14\x4e\x21\x28\x81\x40\xea\ +\xd3\x52\x56\x1e\x84\x7c\x9b\xd8\xdc\x66\x6a\x66\xf5\x90\x6f\x1f\ +\xdf\x80\x4d\x6b\xd5\x2a\xa5\x52\x99\x5a\xb5\x4a\x21\x04\xae\xff\ +\xe9\xa3\x2c\x19\xe8\xc7\xcc\xd5\x61\xec\x68\x5e\xaa\x48\x2b\x27\ +\x9b\x3b\x40\xcd\xbf\x21\xa7\xae\xa4\x31\x57\xe7\xc8\xf0\xfe\xd1\ +\xe1\x89\x89\x95\xdb\x55\xbd\xa8\x2a\xb7\xdc\x72\x4b\x9f\x6a\x0e\ +\x0f\x4d\x06\x5c\xc0\x82\x8d\x46\x43\xd2\x74\x5a\x8e\xcd\x25\x6d\ +\x6b\xfa\x57\xbf\x70\xde\x96\x21\x5b\xad\x55\x5b\x59\x20\x22\x44\ +\x71\x91\x27\x1e\x7f\x9c\xbe\xde\xe5\x74\x76\x76\xd1\xd9\xd9\x41\ +\xb1\x58\xc4\xc5\x11\x86\xdc\x73\x62\x20\xdf\xf0\xb7\xff\xe0\x5f\ +\xe9\xdc\x71\x3f\xf1\xd2\x5e\xd8\x7f\x20\x4f\x7a\x93\x97\x2f\xb4\ +\x84\x9e\xf7\x6a\xc6\x27\xb2\x76\x80\xf1\xbd\x2f\x06\x3f\x3a\xf6\ +\xa9\xb3\xd3\xf4\x4e\x07\x70\xc3\x0d\x37\x8c\xbe\x59\x0e\x4a\xf6\ +\x46\x07\x94\xbe\xfc\xd5\xbf\xfd\xcd\xa1\xcd\x5b\x1a\x9b\x3e\xbc\ +\xb9\x54\xaf\xd7\x17\xad\x2b\x14\x8a\x84\x34\xe1\xce\x7f\xb9\x6b\ +\xee\xe8\xd1\xa3\xbe\x50\x28\x84\xc8\x45\x39\x34\x82\x88\x2c\xca\ +\xf7\x2b\x1f\xdf\x55\xee\x2e\x95\x9d\x8e\x4f\xe6\x07\x11\x92\x1f\ +\xbd\x48\x2b\xf1\x5b\x8c\xdd\x24\xaa\x10\xd0\xc9\x29\x8a\xe5\xb2\ +\xa9\xc0\x47\x80\x3b\x9d\x2c\xaa\xd8\x7e\xe1\x32\x40\xbc\x65\xeb\ +\x96\x81\x2b\x3e\xf1\x89\x2f\xf5\x2e\x5b\x7a\xb9\x11\x57\x78\xfa\ +\x99\x67\x68\x34\x1a\xc7\x29\x50\xa0\x5c\xee\xe4\x82\xad\x5b\xa3\ +\x6a\xa5\x72\x28\x8a\xa2\x17\x3b\x97\x74\xbb\x62\x21\x2e\x1b\x63\ +\x22\x63\xc4\x68\xce\xa6\xea\xb5\x7a\xea\xfd\x0f\x9e\xcb\x69\x4b\ +\x1c\xb5\x3a\xcb\x5e\x7a\x29\x87\xe7\xb7\x38\xca\x9c\x9c\xe4\xd0\ +\x96\x2d\xb8\x72\x89\xa0\x7a\x7e\xf3\x5c\xc8\x6c\xdf\xbe\xdd\x8e\ +\x8c\x8c\xd8\x85\x8b\x1b\x8d\x86\x8c\x8d\x8d\x15\xa6\xa6\xa6\x7a\ +\x87\x36\x6d\xbe\xff\xf2\xcb\x7f\x6b\x40\x25\xc8\xb1\x63\x93\x40\ +\xa0\xe8\x8e\x3f\x52\xf2\x14\xac\x65\x68\x68\xc8\x36\xea\xe9\x9a\ +\xdb\xbe\xfd\x2d\xfb\x66\x6c\x1a\xcd\x1c\x5b\x1e\xaa\xd5\xef\x8b\ +\xb5\x59\x75\x7a\x12\xc2\xe7\x35\x38\xda\xa8\x63\xda\xcb\xa8\xea\ +\x1a\x00\x77\xee\xb9\xe7\x9a\x7d\xfb\xf6\xb9\x62\xb1\x68\x8f\xb7\ +\xbe\x88\x14\x8d\x31\xed\x7b\x5e\xd8\xf3\xf3\x9b\xbe\x78\x63\x7f\ +\x5e\x6b\xbe\xf5\xd9\x84\x72\x62\x36\x6d\x2f\xf9\xb4\x5c\xd2\xb4\ +\x56\x13\x27\x39\x49\x9c\x8c\x12\x69\x8a\x3a\x47\x5a\xa9\x02\x0c\ +\x03\xb8\x75\xeb\xd6\x85\x91\x91\x11\x9d\x9a\x9a\xd2\xc5\x6b\xd3\ +\xe0\xbd\xaf\x3b\xe7\x46\x1f\x7b\xf4\xf1\xeb\x7a\x7a\x7a\x3a\xac\ +\xb5\xf6\x04\xe1\xd6\x92\xdf\x7b\xef\x27\x27\x27\xa7\xa3\x28\x9a\ +\xfb\xcc\x67\x3f\x75\x9d\xc1\xac\x18\x3b\x7c\xe4\x93\xe3\xe3\xd3\ +\xc7\x8e\x56\x8f\xd6\x3a\x5d\x64\xd7\x54\xab\xff\x94\x4c\xcf\x6c\ +\x15\x31\x8c\xf4\xf7\xb7\x10\x68\x11\x41\xe6\x30\xdd\xec\x55\x15\ +\xe9\xee\xa2\x31\x33\xa3\xa8\x3e\x02\xe0\xee\xba\xeb\xae\x00\x34\ +\x6e\xbc\xf1\xc6\xb5\xd6\xf1\x93\x99\xd9\xd9\x15\xb5\x7a\x23\xee\ +\xed\xed\x61\x7c\x7c\x82\xb7\xd3\xd7\x6b\xb5\x7a\xa1\x54\x2a\x8c\ +\x8f\x4f\xb0\xb4\xa7\x87\xc4\x37\xe6\x9e\xdf\xb3\xa7\x30\x35\x7e\ +\x4c\xbb\x7b\xbb\x2e\xae\xd7\xe6\x1a\x51\x64\x1f\x7b\xf4\x89\x9f\ +\x5d\xf6\xf1\x4a\xf5\xdf\xeb\x93\x53\x9b\xa3\xfe\x55\x85\x30\x3d\ +\xdd\x42\x9f\x37\x62\xf1\x96\x02\x80\x29\x95\x48\x46\x5e\xaf\x00\ +\xff\x09\x60\x34\xbb\x7c\x1a\xea\x7f\xb3\x72\xd5\xaa\xe1\xee\x25\ +\xdd\xd1\x19\x1b\x37\x60\x44\x78\xbb\xfd\xe0\xe0\x66\xe7\x9c\xd1\ +\xbe\xde\x5e\x35\xd6\xe8\xc6\x0d\x67\x58\x9f\x7a\x59\x7b\xda\x80\ +\xf1\xa9\xb7\x6b\xd7\xae\x29\x46\x51\x3c\x70\xe6\x59\x67\x5c\x99\ +\x84\x70\x6b\x75\x7c\x7c\xa6\x51\xab\x41\x47\x7b\x76\x14\x79\x7c\ +\x0b\xf3\x7b\xe3\x10\x02\xf4\x74\x33\x37\x39\xa9\x8d\x4a\xe5\xf5\ +\x69\xf8\x41\x13\x65\xb8\xfe\xfa\x6b\x7e\xc5\x88\xdd\x76\xda\xba\ +\xd3\x36\xad\xee\xef\x17\x9f\xa4\xac\x5b\xbb\x86\xa4\xd1\xa0\xb3\ +\xa3\xfd\x4d\xfb\xe6\xba\x66\x3f\x3b\x33\x6d\xcf\x39\xfb\x43\xd2\ +\xd3\xdb\x23\xa7\x9f\xbe\x41\xba\xba\x3b\xa3\xd5\xab\xfb\x5b\xf7\ +\xa7\xa7\xa7\xe5\xfc\x8f\x6c\x2d\x25\xf5\xc6\xcd\xd7\x0d\x0e\x46\ +\xcf\x85\x70\xed\xd4\x2b\xaf\x56\x43\x7b\x1b\xda\xdd\x95\x97\x24\ +\x59\xf3\xde\xe3\x9b\x4a\x68\x40\x7a\x7b\x08\x51\xcc\xcc\x81\x03\ +\xb5\x47\x55\xaf\xbe\x30\x3f\x98\x76\x00\xf5\x46\xfa\xd7\x9f\xbb\ +\xfa\x77\x5e\x3b\x78\xe8\xe0\x39\xcf\x3c\xfd\x0c\x81\xc0\xf0\x6b\ +\xfb\xb3\x5d\x24\x82\xe6\x67\x95\xe4\xa7\xc7\xb2\xa0\x9c\x1b\x19\ +\x19\x41\x55\x39\x70\x60\x84\xcd\x83\x9b\x99\x9e\x99\xe5\xe0\xeb\ +\xaf\x33\x31\x31\xce\xe9\x1b\x36\x32\x76\x64\x8c\x43\x87\x0e\x31\ +\x3c\x3c\x4c\x50\xe5\x92\x4b\x3e\xbe\x6a\xd3\xe0\xe0\x73\x87\x0e\ +\x1f\xbc\xfe\xca\x5d\xbb\x6e\xbf\xaf\xaf\xef\xc7\xfa\xc2\xde\xcb\ +\xba\xd6\xac\x76\x6e\xf5\xa9\x84\x63\xd3\xe8\x5c\x1d\x1a\x0d\x88\ +\x63\xa4\x58\xc0\x74\x77\x51\x9f\x9a\x62\xf6\xd5\xe1\xe4\x95\x95\ +\xab\x6e\xbf\xfe\xc0\x81\x03\x40\x59\x44\x12\x77\xd5\x55\x57\x2d\ +\x2b\x94\xe2\x0b\xd7\xaf\xdf\xd0\xbe\xfe\x97\xd6\xb3\xf5\xfc\x0b\ +\x5a\x02\x8a\x2c\x16\xba\x49\x19\xad\xf9\xe6\xce\x70\x41\x5e\x2b\ +\x0a\x57\x7c\x7a\xc1\x39\xa9\x2e\x38\x24\xcb\x96\x0e\xf4\x0f\x9c\ +\x72\xef\xbd\xff\xf6\xbb\xc0\xed\x23\x37\x7f\xf1\xd5\xd9\xff\xda\ +\xfd\xe0\xc0\x77\xbf\xb7\xad\xd0\xde\x16\x95\x3a\x3b\xad\x5d\xda\ +\x83\x29\x15\x09\x95\x2a\xc9\xcc\x0c\xb5\x7d\xc3\xbe\xee\x7d\xf5\ +\xd5\x6b\x3f\xff\x93\x89\xf5\xeb\xc6\xb9\xf6\x0f\xe6\x91\x55\x55\ +\x67\xa6\xa6\x26\x0f\x5f\x73\xed\xef\x2d\xb7\xce\x9d\x24\x20\xbf\ +\xbb\x2b\x69\x24\x85\xe0\xfd\xc3\x40\xf5\xb5\xfd\xfb\x77\xca\x79\ +\x5b\xdc\xf3\x67\xfe\xf2\xd8\xb2\xfb\x77\x6c\xec\xd8\x37\xbc\xa2\ +\xf8\xf2\xbe\xa5\xb6\x52\x29\xa5\x9d\x1d\x95\xca\x29\xcb\xc7\x66\ +\x87\x36\x1f\x1c\xbd\xf4\x63\x7b\x4d\xa9\x78\x6c\xef\xb3\x3f\x7f\ +\x24\x2f\x72\xaa\xda\xac\x85\x2e\xbc\xf0\x42\xd7\xdd\xdd\xdd\xce\ +\xff\xe2\x75\xf7\xdd\x77\x4f\x89\x88\x05\xda\x2f\xbe\xf8\xe2\xbe\ +\x73\x36\x9d\xb3\xa1\xbd\xbd\xdc\xd7\x56\x2e\x2e\x8d\x8b\xa5\xde\ +\x38\x8e\xda\x1a\xf5\x64\xba\x3e\x57\x1d\xaf\x54\xe7\x8e\xce\x55\ +\x6a\x47\x76\xee\x7c\x64\xcf\x63\x8f\x3d\x36\x01\x54\x34\x3f\x5f\ +\x97\xff\x2b\xff\xad\x22\x22\x06\x88\xf2\xbc\x6c\xf2\x4d\xc8\xb6\ +\x71\xa4\x40\xda\x14\x7a\xd1\x73\x1f\xf4\x7f\xb7\xf9\x1f\xc2\x26\ +\x56\xd5\x70\x45\xfc\x8a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x0b\xd7\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ +\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0e\x74\x45\ +\x58\x74\x54\x69\x74\x6c\x65\x00\x43\x6f\x6d\x70\x75\x74\x65\x72\ +\xf8\x18\x12\x76\x00\x00\x00\x17\x74\x45\x58\x74\x41\x75\x74\x68\ +\x6f\x72\x00\x4c\x61\x70\x6f\x20\x43\x61\x6c\x61\x6d\x61\x6e\x64\ +\x72\x65\x69\xdf\x91\x1a\x2a\x00\x00\x0b\x17\x49\x44\x41\x54\x68\ +\xde\xe5\x9a\x6b\x6c\x1c\xd7\x75\xc7\x7f\xe7\xde\x3b\xb3\x0f\x52\ +\x24\x45\x9a\x54\x15\xc3\x7a\x50\xf1\x0b\x6e\xed\x24\xaa\x25\x51\ +\xa9\x95\x5a\x40\x12\xd4\x71\x81\x02\x89\x03\xa3\xaf\x00\x4e\xd0\ +\xda\x75\x61\x03\x82\xd3\xd8\x7d\xa0\x68\xd3\x7e\x28\xea\x18\x0d\ +\x8a\x3e\xd2\xc4\xf5\x97\xc4\x4e\xea\x0f\x8a\x8d\x54\x34\x1a\x19\ +\x74\x5b\xc4\x70\x22\xb7\x31\x1a\xc7\x52\x52\x47\xa1\x64\x5a\x12\ +\xc5\xa7\x44\x72\x97\xbb\x33\xf7\x9e\x7e\xb8\xb3\x4b\x52\x92\xf5\ +\xa2\x10\xc1\xe8\x00\xc3\xb9\x3b\x3b\x3b\x7b\xfe\xf7\x3c\x7e\xe7\ +\xce\xd2\xa9\x2a\xef\xe6\xcd\xbd\xab\xad\xff\x7f\x2f\x60\xf8\xdb\ +\xcf\xff\x2d\x2a\x0f\xa2\x48\xd0\x40\x08\x81\x10\x14\x5d\x3e\xf6\ +\x81\x40\xc0\x7b\x45\x43\x20\xa8\x8f\xe7\x43\x71\x8d\x2a\xde\xfb\ +\xe2\xb5\x12\x34\x10\xbc\x6f\x8f\xe3\x7b\xc5\xf9\x10\xd0\xa0\x78\ +\x1f\x82\x48\xf8\xec\x9f\xfc\xd1\x9f\x3f\x71\xd9\x02\x86\x87\x87\ +\xbb\x30\xfc\xee\x07\x77\xee\x12\x63\x0c\x22\x06\x11\x41\x10\x44\ +\x80\x62\x0c\x8a\x6a\xdc\xa3\xc1\x85\xb0\xe0\xf1\x2d\x11\xc1\xe3\ +\x7d\x3c\xe6\xde\x13\x7c\x4e\xee\x3d\x3e\x2f\x8e\xc5\x6b\x0d\x01\ +\x55\xa5\x5e\xaf\x9b\x91\x97\x46\x3e\x0f\x5c\xbe\x80\x46\xa3\x61\ +\x4a\x55\x1b\x16\x6a\x0d\x6e\xf9\xd8\xe7\x08\xab\xae\x05\x8a\xc6\ +\xc3\x79\xb7\xce\x8a\x63\xef\x17\x3f\x4d\xf0\x6a\x56\x1d\x42\xed\ +\x30\x41\x19\xd8\xb2\x0d\x05\xb4\xb0\x42\x95\x38\xf3\xad\x73\xcb\ +\xc6\x1b\xd7\x77\x83\x82\xf7\x9e\x3c\x28\xcd\xcc\x73\x72\x66\x01\ +\x55\xe8\xa8\xa4\xdc\xb0\xa1\x97\x50\xcc\x48\x0c\xa5\xb8\x67\x59\ +\xc6\xff\x7c\xef\xdf\x49\x92\x84\xa0\x81\x2b\x23\x40\x15\x30\xa8\ +\xd8\x15\x33\xa9\xc5\x48\xcf\x38\xe7\x9c\xc1\x5a\x47\xee\x03\x2a\ +\xa0\xa2\x2c\x34\x32\x02\x16\x15\x58\xd7\xb7\x86\x10\x84\x50\x88\ +\x0d\x45\x18\xc6\xdd\x00\x90\x24\x29\x21\x5c\x11\x01\x31\x96\x61\ +\xe5\x6c\xb7\xc6\x68\xe1\x89\xd6\xfb\x0a\x69\xd9\x90\xfb\xe8\xb9\ +\x3c\x04\x72\xaf\xcc\xd7\x33\x54\x95\x72\xea\x48\x13\xcb\x62\xe6\ +\x41\x21\x14\x9e\x04\x25\xc4\xe4\x05\x20\xbd\x32\x02\x66\xc9\xf3\ +\x0e\x9a\x59\xd6\xfe\x82\x25\x11\xd1\xea\xe5\x86\x6b\x71\x22\x35\ +\x26\x86\x8e\x8f\x02\x16\xea\x4d\x72\x1f\x00\xa5\xb7\xab\x4c\xa3\ +\xe9\xa3\xe7\x74\x29\x1f\x82\x46\x5f\xfa\xdc\x17\x1e\x48\xae\x8c\ +\x07\xbc\xcf\xc9\x9a\x19\xa8\xe2\x7d\xde\x36\x72\xc9\x13\xac\x30\ +\x46\x51\x8c\x51\x9a\x59\x86\xf7\xe0\x15\xe6\x6b\x0d\x34\x28\x69\ +\x6a\xb1\xd6\xd0\xcc\x63\xd9\x6c\xdf\xa3\xf8\x7c\x50\x45\xfd\x15\ +\x14\x30\x0b\xd8\x46\x83\xb9\xf9\xd3\x68\x9e\xe1\xb3\xc5\x22\x94\ +\x96\xbe\x18\xa4\x7d\x7d\x51\x59\x11\x72\x7c\xa6\x84\x00\x8d\x3c\ +\x90\xe7\x39\x22\x96\xae\x6a\x89\x2c\xf7\xa8\x4a\x7b\xc6\x51\xda\ +\xc9\xac\x45\xd2\x03\x38\xe7\xda\xa1\xeb\x56\x11\x41\x2c\x64\xf3\ +\x32\x31\x71\x12\xcd\x17\x09\xcd\xf9\xc2\x50\x83\x08\x98\x08\x83\ +\x25\xeb\x81\xc4\x5a\xb4\xb1\x80\x2f\x92\x33\x6b\x78\x12\x31\xb8\ +\xd4\x52\x4e\x1d\x8a\xd0\xca\xd9\xa0\x10\x7c\x01\x2e\x0d\xf8\xb0\ +\xe4\x01\x6b\x1d\xbe\xe5\x81\xcb\xa5\xa9\x38\xa5\x6c\xca\x8c\x9f\ +\x78\x8b\x2f\x3c\xf2\x91\x4b\xa0\x69\x1d\x0d\x4a\xee\x03\x07\x46\ +\x95\x1f\x8c\x57\xe8\x2c\x57\xc1\x28\x8a\xa5\xed\x38\x0d\x50\x54\ +\xa9\x56\x1e\x15\x29\x80\x35\x86\xe0\xb5\xf0\x40\xb8\x6a\x34\x25\ +\xf3\x23\xfc\xe8\xa4\xc7\x19\x40\x0d\xc6\x18\x54\x04\x89\xbe\xc0\ +\x20\x04\x42\x91\x13\x8a\x4a\xcb\xa3\x82\xb6\x39\x20\x72\xd5\x68\ +\xaa\x5e\x49\x5c\xfc\x80\x18\x83\x88\x25\x88\x80\x2a\x22\x85\xf1\ +\xad\x10\x34\xbe\x1d\x8b\x71\xa2\x0a\x01\x57\x9b\xa6\x06\x10\x11\ +\x8c\x18\xd4\x44\x18\x8a\x01\x54\xe2\x8c\xb7\x66\x44\x4d\xe1\x13\ +\xf0\x21\x3f\x43\xc0\x55\xa4\xa9\x4d\x12\x9c\xb3\x18\x67\xc1\x38\ +\x02\xd2\x06\x56\xdb\x91\x01\x54\x04\x13\xdb\x1f\xf2\x6c\x85\x80\ +\xab\x47\x53\xd5\x40\xe2\x2c\x2e\x49\x70\x2e\x45\xad\xc3\x2b\x31\ +\x17\x8d\x20\x26\x20\xe2\x11\x2f\x80\x87\x10\x27\x38\xcb\xb2\x25\ +\x01\xde\x07\xf2\xa2\x3c\x85\x15\x21\x73\x1e\x9a\x5a\x4b\xfc\x9c\ +\x92\x07\xbd\x6c\x9a\xe6\x79\xce\x8f\x5e\x7d\x11\x93\x54\x91\xa4\ +\x8c\x18\x57\xc4\x4f\x2b\x10\x74\x45\x48\x8b\xc4\xdc\xc8\xf2\x66\ +\x3b\x3c\x5d\x08\x9e\x50\xcc\x4a\x2b\x17\xce\x65\xf8\x72\x9a\x5a\ +\x6b\xc8\x7c\x2c\xab\x3e\x28\xf3\xf5\x26\xaa\x4a\x9a\x5c\x1a\x4d\ +\x8d\xc0\x17\x1f\xfb\x24\xbd\x7d\xbd\x74\x77\xf7\x50\x2a\xa5\x18\ +\x63\xda\x65\xd8\x87\x80\xf7\xa1\xa8\x76\x71\xa7\xc8\xa3\x15\x39\ +\x10\xdf\xa0\xc8\x85\x33\x66\x3c\x32\x34\x52\x54\x04\x44\x71\x89\ +\x6d\xd7\xe1\xa6\xf7\xf8\x10\xdd\xde\xd5\x51\x22\xcb\xc3\x92\xb1\ +\x17\xa0\x29\xaa\x74\xaf\xe9\xa4\xb7\xa7\x8b\xb5\x6b\xbb\xa9\x54\ +\x2a\x18\x13\x4b\xa4\x0f\xb1\x64\xc7\x10\x0e\xfc\xf0\x8d\x83\xbc\ +\xfa\xea\xab\xec\xde\xbd\x9b\x7a\xad\x46\x28\x6e\xea\x62\x7d\xce\ +\x96\x90\x2f\x60\xc4\x42\xcb\x60\x96\x6a\x2f\x28\x69\xe2\xb0\xc6\ +\xc6\xa4\x16\xc5\x37\x72\xd2\x24\xc5\x5a\xa1\x52\x4a\x8b\x49\xd0\ +\x8b\xa2\x69\x9e\xe7\x3c\xf7\xdc\x5e\xaa\x9d\x1d\x54\xaa\x15\x12\ +\xe7\xea\x91\x35\x11\x7a\xcb\xc1\x58\x5b\x58\x48\x2b\x95\xaa\x7c\ +\xe3\xeb\xcf\x04\x44\x02\xca\xdf\x45\x01\x41\xf1\x79\xde\x76\x6b\ +\xec\x57\x0c\x18\x96\xa0\x46\x9c\x79\x10\xca\xa5\x04\x63\x6d\xe1\ +\x98\x80\x18\x4b\xa9\x64\xe8\x28\x27\x88\x8d\x15\x83\xa0\xf1\xfa\ +\x0b\xd0\x54\x15\x6e\x7d\xdf\xad\x0c\xac\xfb\x39\x8e\x1c\x19\x6d\ +\xbc\xf1\x83\x43\x37\x56\xab\x55\x5f\x2e\x97\x03\xd4\xa8\x01\xf1\ +\x0f\x80\xa1\x36\xbf\x08\xd4\x69\x34\x5c\xfe\xf4\xd3\x4f\x4f\x02\ +\x38\x0d\x31\x89\x15\x48\x9c\x5d\xa2\xb1\x91\xb6\x00\xa4\x20\xb3\ +\x0a\x95\x52\x82\xb1\x82\x18\xc8\x9b\x9e\x24\x49\xb1\x02\x69\xea\ +\x08\x41\x31\x26\xe2\x47\x8a\xe2\x79\x3e\x9a\x82\xf2\x0b\xb7\xdc\ +\xc6\x86\x0d\x1b\x19\xe8\xef\xd3\x24\x71\x3b\x1f\xfb\x83\x3f\xfe\ +\x57\x60\x11\xf0\x7a\x11\x0f\xad\x5c\x8c\xb3\x78\x3f\xe7\x1c\xc6\ +\x14\x25\x4c\x4c\x11\xf7\xa6\x68\xcc\xa2\x47\xd2\x34\x45\xd0\x48\ +\x6d\x0b\xa5\x54\x70\xce\x14\xf7\x50\x8c\x17\xd4\x28\xa8\x5c\x90\ +\xa6\xde\x7b\xde\x7c\xf3\x27\x2c\xd4\x6a\x74\x74\x76\x94\xdf\xb3\ +\xfe\x3d\x5f\xfb\xd2\x3f\xfd\xfd\x68\xb9\x5c\x3a\xd8\xdd\xdb\xe3\ +\x5e\xf8\xb7\x6f\x75\x18\x63\x12\x11\x6c\x88\x8d\x67\x8e\xea\x42\ +\xd0\xf0\x1f\x9a\x2d\xfe\xc3\x5d\x77\xdd\x73\xc2\x85\x10\x68\xe9\ +\x74\x89\xc3\x14\x06\x8b\x2d\x8e\x62\x30\xa6\x08\x24\x23\xb8\xc4\ +\xb5\xea\x2d\x69\x22\xf1\xb6\x05\x65\x51\xc5\xab\x81\xe0\x11\x23\ +\x17\x45\xd3\xb4\x6c\x11\xe3\x49\x4b\x96\x6d\xdb\xb6\xd9\xe0\x19\ +\x7c\xf2\x9f\xbf\x92\x04\xe5\xfe\x0d\xd7\xae\x9f\xea\xef\xef\xcf\ +\x93\x24\xb5\x31\xea\x8c\x33\xa9\x5d\x5b\xad\x54\xee\xb5\x69\xf5\ +\x69\x60\x77\x14\x50\x14\x6b\x6b\x2c\xc6\x18\xac\x35\x85\xe1\x06\ +\xdb\xf2\x86\x89\x42\xac\x35\x60\x81\x10\x28\x9b\x22\xe4\x8b\xee\ +\xd5\xfb\x80\x18\x45\xbc\x69\x57\x9b\xf3\xd1\x34\xcf\x73\xbe\xf9\ +\xcd\xe7\xf2\x24\x49\xd4\xda\xf8\xdd\x20\x66\x6e\x6e\xee\xc0\xf7\ +\x5e\x39\xf0\xd6\xd0\xd0\xd0\xdc\x67\xee\xff\xd4\x9f\x3a\x9b\xfe\ +\xb2\x20\x15\xd0\xb2\x0a\x3e\x84\x90\x18\x23\xdf\x12\x11\x71\xad\ +\x52\x15\x2b\x83\xc1\x2c\x13\x61\x8d\xc1\x58\x83\x29\x04\x38\x2b\ +\x45\xec\x0a\x56\xa5\x30\x6e\x89\x07\xd6\x04\xf2\xe0\x23\x3d\x5b\ +\x79\x74\x7e\x9a\x36\x7f\x7c\xe8\xcd\xa1\xe5\xdd\xa0\xf7\xde\xcf\ +\xcc\xcc\x9c\x4e\x92\x64\xf1\xb7\x3e\xf5\xeb\x0f\x19\xcc\xfa\x89\ +\x13\xe3\x9f\x9c\x9a\x3a\x7d\x6a\xb2\x36\x59\xef\x72\x89\x5d\xbb\ +\xb6\xbb\xf1\xd2\x4b\x07\x26\xda\x49\x8c\x2a\x8d\x46\x83\xff\x3d\ +\xf0\x02\x2d\xb3\xda\xed\x4b\xbb\x3b\x5a\xb9\xc9\x3b\x9c\x3f\xbb\ +\x41\x7d\x67\x9a\xaa\x92\xde\xbe\x63\xdb\x7f\xad\x5b\x37\x40\xc8\ +\xf3\x46\xa5\xb3\xa3\x34\x3d\x3d\xc3\x35\xbd\xbd\x64\xbe\xb9\xf8\ +\xc6\xa1\x43\xa5\xd9\xa9\x53\xda\xd3\xd7\xfd\xe1\x46\x7d\xb1\x99\ +\x24\xf6\xe5\xef\x7c\xf7\xbf\x3f\x36\x3c\x3c\x9c\x47\xb8\xab\xba\ +\x10\x02\xd5\x6a\xc2\x0b\x5f\x7a\x80\x96\x1b\x63\x28\xc5\xf1\xf2\ +\x5d\xa4\x55\x99\x5a\xa4\x2e\xea\x74\xb1\x1e\x88\x63\x4f\x0b\x8e\ +\x17\x43\xd3\xfe\x6b\x7a\x69\x36\x1a\x6c\xdd\xba\xd5\xfd\xe4\xf0\ +\x9b\x3a\xd0\xd7\x07\x46\xb8\x69\xcb\xcd\xf6\xf0\x4f\x0f\xcb\xe6\ +\x2d\x1b\x65\x72\x72\x8a\xcd\x9b\x37\x95\xc7\x4f\x9c\xdc\x78\xcb\ +\xad\x37\xff\xf6\xbe\x7d\xfb\xbe\xdc\xae\x42\x79\x96\x1b\x55\x65\ +\xa0\xaf\x37\x86\x8b\x11\xac\x58\xc4\x5a\x8c\x48\xdb\xf0\x73\x0b\ +\x68\x2d\x72\x96\x56\x70\xf1\xb5\x8f\x0b\x9c\x0b\xd0\x54\x55\x31\ +\x22\x6c\xda\xb4\x91\xda\xc2\xbc\x7d\xff\x6d\xef\xe3\xc8\xd1\xa3\ +\x0c\xac\x1b\xa0\x52\xa9\x24\x1b\x36\x5c\xc7\xdc\xa9\xd3\x0c\x6e\ +\xde\xc4\xd4\xd4\x94\xdc\x71\xc7\x1d\x95\xe7\x9f\x7f\xee\xcf\xf6\ +\xec\xd9\xf3\xd5\x27\x9e\x78\xa2\x5e\xac\x89\xe5\xf9\xfd\x2f\xbe\ +\x78\x37\x68\x60\x05\x05\x97\x1b\x17\xb1\x1a\x08\xe4\x3e\xaf\xc4\ +\xf6\x20\x14\xcb\xc4\xd6\x32\x53\xe3\xac\x87\x62\xf9\x78\x0e\x9a\ +\xd6\x6b\x35\x2a\x95\x2a\x5f\x7f\xe6\x19\xc4\x08\x79\xee\x99\x9e\ +\x9e\x66\x61\xa1\xc6\xce\xa1\x9d\xcc\xcd\x2f\x70\xfc\xd8\x31\x66\ +\x66\xa6\xb9\xf1\x86\x9b\x98\x18\x9f\xe0\xf8\xf1\xe3\x8c\x8e\x8e\ +\x12\x54\xf9\xe8\x47\xef\xba\x76\x68\xe7\x07\x5f\x1f\x19\xd9\xff\ +\x10\xf0\x57\x00\xee\xf7\x1f\x7c\xf8\x13\x8f\x3f\xfe\xf8\x80\x6a\ +\x51\x1e\x5a\x04\x5c\x46\xc1\x66\xb3\x29\x79\x7e\x5a\x4e\x2d\x66\ +\x1d\x9b\xae\xdb\x70\x70\xe7\x8e\x21\x5b\xab\xd7\xda\x59\x20\x22\ +\x24\x69\x99\xef\xbe\xf2\x0a\x03\x7d\xeb\xe8\xea\xea\xa6\xab\x6b\ +\x0d\xe5\x72\x19\x97\x26\x18\x0a\xcf\x89\x81\x62\xc1\x6f\xad\xa3\ +\xa7\xa7\x87\x72\xb9\x4c\xb5\x5a\xa5\x54\x2a\x15\x05\x43\x96\xad\ +\xea\x74\x59\x5b\x1f\x13\x6f\x70\xf3\xe0\xfa\xe1\xe1\x7d\xf7\xb7\ +\x05\x00\x3c\xf2\xc8\x23\x27\xdf\x29\x07\x25\x36\x44\x0e\xa8\x7c\ +\xe1\x6f\xfe\xfa\x57\x87\xb6\xef\x68\xde\xfe\x8b\xdb\x2b\x8d\x46\ +\x63\xc5\x75\xa5\x52\x99\x90\x67\x7c\xe3\x5f\x9e\x5d\x9c\x9c\x9c\ +\xf4\xa5\x52\x29\x24\x2e\x29\x4a\x23\x88\xc8\x39\xf2\x7d\xa9\x49\ +\x14\x23\x17\xb5\x68\x6d\x36\x9a\xa5\x3c\xcb\x47\xda\x39\x20\x2b\ +\x3a\xb6\xb3\x36\x03\xa4\x3b\x76\xed\xd8\x78\xef\xc7\x3f\xfe\xf9\ +\xbe\xfe\x6b\xee\x36\xe2\x4a\xdf\x7f\xed\x35\x9a\xcd\xe6\x19\x02\ +\x4a\x54\xab\x5d\x7c\x68\xd7\xae\xa4\xb6\xb0\x70\x3c\x49\x92\x1f\ +\x77\xad\xed\x71\xe5\x52\x5a\x35\xc6\x24\xc6\x88\xd1\x82\xa6\xea\ +\xb5\xd6\xc8\x1a\x2f\xbf\x7d\xe4\xe8\x93\xfb\xf7\xff\xe7\xc9\x4b\ +\x5d\x79\xef\xdd\xbb\x77\xb6\x2d\x00\x30\xf7\xdc\x73\x8f\x1d\x1b\ +\x1b\xb3\x2b\x94\x36\x9b\x32\x31\x31\x51\x9a\x9d\x9d\xed\x1b\xba\ +\x7d\xfb\xbe\xbb\xef\xfe\xb5\x8d\x2a\x41\x4e\x9d\x9a\x01\x02\x65\ +\x77\xe6\x23\x25\x4f\xc9\x5a\x86\x86\x86\x6c\xb3\x91\x6f\xfa\xca\ +\x93\x5f\xb6\x17\xa2\xe9\x7b\x6f\xbc\xf1\x1f\x1f\x7c\x70\xcf\xee\ +\xd5\x3c\x46\x70\x5b\xb7\x6e\x35\x87\x0f\x1f\x76\xe5\x72\xd9\x9e\ +\x39\xfb\x22\x52\x36\xc6\x74\x1e\x3a\x78\xe8\xf5\xc7\xfe\xf0\xd1\ +\xeb\x8a\x5e\xf3\xc2\xcf\x26\x94\x4b\xa2\xa9\xae\xe2\x97\x46\x37\ +\x38\x38\x18\xc6\xc6\xc6\x74\x76\x76\x76\xc5\x4d\xf2\x3c\x0f\xde\ +\xfb\x86\x73\xee\xe4\xcb\xdf\x79\xe5\xa1\xde\xde\xde\x35\xd6\x5a\ +\x7b\x9e\x70\xbb\x6c\x9a\xae\xca\x03\xcf\x3e\xfb\x6c\x00\x9a\x8f\ +\x3e\xfa\xe8\x66\xeb\xd8\x3f\x37\x3f\xbf\xbe\xde\x68\xa6\x7d\x7d\ +\xbd\x4c\x4d\x4d\x73\x29\xc7\x46\xbd\xde\x28\x55\x2a\xa5\xa9\xa9\ +\xe9\x4b\xa2\xe9\xaa\x04\x14\x37\xf0\x9f\xfd\xdc\x9e\xbf\xbc\xee\ +\xda\xc1\xd1\xf1\x93\xe3\x1b\x36\xf5\xf4\x30\x39\x39\xc5\xcd\x37\ +\xdd\x70\x49\xc7\x6d\xdb\xb6\xbb\x1f\x1e\x7c\xfd\xfc\x34\x1d\x1f\ +\x3f\x8b\xa6\xab\x12\x00\xf0\xf0\xc3\x0f\xfc\x7c\xb9\xd2\xb9\x7b\ +\xcb\xe0\x96\x6a\xa9\x94\xca\xcc\xf4\x4c\x8b\x7e\x74\xad\xe9\x24\ +\x6b\x36\xcf\x79\xf4\x59\xce\xe0\xe6\x4d\xb4\xae\x9f\x9f\x3b\x6d\ +\x3f\x70\xdb\xfb\xf9\xe9\xe8\x28\xfd\x03\xfd\xe7\xa6\xe9\x2f\xed\ +\x3a\x8b\xa6\xab\x16\xd0\x68\xe6\x7f\xf1\xe9\xcf\xfc\xe6\xd1\x63\ +\xc7\x8f\x7d\xe0\xb5\xef\xbf\x46\x20\x30\x7a\xf4\x48\x5c\x45\x22\ +\x68\xf1\xac\x92\xe2\xe9\xb1\x2c\x6b\xe7\xc6\xc6\xc6\x50\x55\xde\ +\x7a\x6b\x8c\xed\xdb\xb6\x73\x7a\x6e\x9e\x63\x6f\xbf\xcd\xf4\xf4\ +\xd4\x3b\xd0\xf4\x57\xce\xa2\xe9\xaa\x04\xdc\x77\xdf\x7d\xfd\xa5\ +\x4a\x7a\xe7\xf5\xd7\xdf\xd0\x79\xfd\x7b\xaf\x67\xd7\x1d\x1f\x6a\ +\x1b\x18\x17\x62\x4b\x46\xb7\x90\xd1\x3e\xdf\x5a\x19\x2e\xcb\x6b\ +\x45\xe1\xde\xdf\x60\xe9\x39\xe9\xd9\x34\xdd\xb2\x79\xcb\x0a\x9a\ +\xae\x36\x07\xe6\x66\x67\x67\x4e\x3c\xf0\x7b\xbf\xb3\xce\x3a\x27\ +\xfc\x0c\xb6\xac\x99\x95\xb2\x66\x36\x72\x45\x72\xe0\xa9\xa7\x9e\ +\x5a\xbc\xf3\xce\x3b\x6f\xe9\xe9\xe9\xe9\xe4\x67\xb8\x2d\xa7\xe9\ +\xaa\x73\x60\x64\x64\x24\x27\xfe\x6a\xf4\xae\xdb\xde\xf5\xff\xec\ +\xf1\x7f\x9d\x3d\x46\xc4\x32\x49\xfc\x0b\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\xe7\ +\xff\ +\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\ +\x48\x00\x00\xff\xdb\x00\x43\x00\x02\x01\x01\x01\x01\x01\x02\x01\ +\x01\x01\x02\x02\x02\x02\x02\x04\x03\x02\x02\x02\x02\x05\x04\x04\ +\x03\x04\x06\x05\x06\x06\x06\x05\x06\x06\x06\x07\x09\x08\x06\x07\ +\x09\x07\x06\x06\x08\x0b\x08\x09\x0a\x0a\x0a\x0a\x0a\x06\x08\x0b\ +\x0c\x0b\x0a\x0c\x09\x0a\x0a\x0a\xff\xdb\x00\x43\x01\x02\x02\x02\ +\x02\x02\x02\x05\x03\x03\x05\x0a\x07\x06\x07\x0a\x0a\x0a\x0a\x0a\ +\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\ +\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\ +\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\xff\xc0\x00\ +\x11\x08\x00\x30\x00\x30\x03\x01\x22\x00\x02\x11\x01\x03\x11\x01\ +\xff\xc4\x00\x1c\x00\x00\x02\x02\x02\x03\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x07\x08\x06\x09\x00\x05\x02\x03\x04\xff\xc4\ +\x00\x30\x10\x00\x01\x03\x03\x03\x03\x02\x05\x04\x02\x03\x00\x00\ +\x00\x00\x00\x01\x02\x03\x04\x05\x06\x11\x00\x07\x12\x08\x21\x31\ +\x09\x13\x14\x22\x41\x51\x71\x23\x42\x61\x81\x15\x32\x43\x92\xa1\ +\xff\xc4\x00\x19\x01\x00\x02\x03\x01\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x05\x06\x02\x03\x04\x00\xff\xc4\x00\x2f\x11\ +\x00\x01\x02\x04\x04\x03\x06\x07\x01\x00\x00\x00\x00\x00\x00\x00\ +\x01\x02\x11\x00\x03\x04\x05\x12\x21\x31\x41\x51\x61\x91\x06\x13\ +\x14\x22\x81\xb1\x23\x32\x42\x71\x82\xa1\xc1\xd1\xff\xda\x00\x0c\ +\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xbf\x0d\x66\xb5\xf7\x25\ +\xd9\x6c\x59\xf0\x17\x54\xba\xee\x18\x54\xd8\xed\xb6\xa7\x16\xec\ +\xd9\x29\x6c\x04\xa4\x65\x47\xe6\x3d\xf1\xfc\x69\x20\xea\x83\xd6\ +\x4a\xc8\xb6\xab\x2f\xd8\xbb\x2b\x12\x4b\xfc\x1c\x53\x4e\xd7\xdd\ +\x6c\x21\x2a\x23\xea\xd0\x57\x84\xe7\xf7\x11\x93\xfc\x76\xd6\x0a\ +\xeb\x95\x1d\xbd\x18\xa7\x2d\xb9\x6e\x62\x2a\x5a\x51\xa9\x87\xb9\ +\xc7\x10\xd0\x25\xd5\xa5\x20\x0c\x9e\x4a\xc6\x07\xf7\xa8\x95\xf7\ +\xbf\x1b\x3d\xb6\x8b\x0c\x5e\xbb\x87\x4d\x86\xfa\x81\x29\x88\x1e\ +\xf7\x5f\x57\xe1\xb6\xc2\x95\xff\x00\x9a\xa7\xcb\xf3\xd4\x1a\xea\ +\xac\xbd\x29\xfa\xac\xd6\x3d\xd7\x1c\x2a\x5b\xb2\x6a\x0b\x5a\x97\ +\xf9\xe7\xe4\xff\x00\x1a\x14\x5d\xdd\x64\x5e\x95\x52\xd2\xa9\xf5\ +\xc6\xd8\xf7\xd6\x0c\x62\xd2\xb8\xe5\x49\xc1\x1c\x70\x06\x49\xed\ +\xdb\xb9\x3e\x34\xad\x51\xdb\x4a\x69\x63\xc8\x8c\xf6\xcd\xff\x00\ +\x59\x7b\xc5\x7d\xeb\xfc\xa1\xe2\xd3\x7a\xb3\xf5\x08\xad\x5b\xd6\ +\xfc\x78\xdd\x3e\xd4\xe9\x54\xc7\x94\xf7\x29\x75\xbb\xad\xa0\xda\ +\x3d\xb1\xe1\x2c\xb6\xb3\xdc\x93\xe5\x4b\xf1\x8f\x1d\xf3\xae\xcd\ +\xbd\xf5\x5c\xd9\x3b\x5e\xd5\xa4\x52\x3a\x97\xbb\x21\x52\xee\x17\ +\x92\x94\xca\x9f\x4e\x4a\x3e\x0d\xf0\x55\x84\xbc\x94\xf3\xe6\x91\ +\x82\x09\x00\x11\x9c\x91\xdb\x03\x55\x2f\x71\xc9\xde\x4d\xc7\xa8\ +\xb7\x3e\xb9\x74\xd6\x22\x30\xcc\x30\x65\xdd\x14\xf9\xb1\xd9\x11\ +\xc2\xdc\x07\xe1\x13\x2a\x53\x6a\x69\xa5\x29\x20\x85\x06\x52\xb7\ +\x4a\x4f\xcb\xe0\xe4\xf3\xd0\xaf\xa3\xa6\xe2\x75\x1d\x29\x8b\xef\ +\x70\xea\x4d\x52\xec\xb1\x39\x4a\xf8\xf7\x1b\x77\xdf\x9a\x80\x73\ +\xed\x44\x43\xa0\x38\x1b\x19\xe3\xee\x2c\x36\x0f\x9e\x2a\xc6\x34\ +\x36\x4f\x69\x2f\x35\x15\xd8\x29\xa5\x95\xad\x5f\x4b\xf9\x40\xe7\ +\x93\x0e\xbf\xe4\x14\x97\x40\xaf\x0e\x99\xeb\x53\x02\x73\x1a\x30\ +\xfe\xbf\x00\x3f\xb1\xb8\xea\x7a\xbb\xbf\x1b\xc1\x6b\x5b\x95\x6b\ +\x7f\x76\xe5\xb9\x2a\xd9\x84\xf4\x09\xb6\xf5\x51\xd2\xeb\x33\xff\ +\x00\xe7\x69\x21\x5f\xec\x1c\x50\xe4\x39\x12\x42\xb8\xa1\x3f\x6c\ +\xa8\x15\x2b\xee\x9f\xb8\x55\xa4\x3b\x31\x4a\x8c\xb0\xe1\xf8\x86\ +\x56\x3f\x77\xd4\x24\x9f\xc6\x30\x71\xa6\x87\xaa\x9a\x8d\xcf\xb0\ +\x5b\xbc\x29\xb2\xdb\x4f\xb2\x92\xb8\x93\xd8\x43\xb8\x38\x8e\xe0\ +\x53\x6b\xc1\xf0\xa4\x82\xd9\x4a\xbc\x10\xac\x13\xe3\x48\xf5\xeb\ +\x7c\x6d\x72\xfa\xac\xaa\xd6\xe4\xc1\xac\xc4\xb6\xab\xce\x09\x60\ +\xc5\x8a\x4a\x5a\x25\x64\x28\x14\x27\x3c\x7b\x83\x82\x3b\x11\xa0\ +\x77\x29\x0b\xab\xab\xee\xd2\x7e\x20\xd5\x24\xea\xcd\xd3\x81\x8c\ +\x52\xad\x93\xea\xea\x17\x29\x45\x8a\x77\xf6\xea\x22\x7b\x72\xd8\ +\x76\x04\x9b\x65\xa9\xb5\xfd\xc1\x94\xca\xde\x6d\x7c\x20\x53\x69\ +\xe6\x4c\xae\x41\x58\xc1\x2a\xe0\xd2\x33\xf4\xe4\xbf\x1d\xf1\xdf\ +\x1a\xe3\xb7\xf4\x86\xe5\xd9\xb3\x2f\x8a\x25\xa7\x06\x9b\x4e\xb3\ +\x96\x1c\x8f\x51\xbb\xa4\x99\xf2\x14\xbc\x10\x12\xdb\x49\x08\x8f\ +\xee\xf2\x50\x23\xe4\x50\x1e\x3b\xe3\x46\xab\x19\x1d\x11\x49\xa4\ +\x44\x90\xfd\xdf\x52\xa8\xad\x08\x0a\x54\x46\x29\x4f\x29\xc5\x2f\ +\xfe\xb8\x1a\xf6\x35\x1f\x6b\xf7\x9b\xa8\x0a\x46\xd6\x50\xe9\x12\ +\xa8\xd4\x08\xe8\x52\xe2\xd2\x26\x41\xc2\x25\xcc\x0d\x85\xa5\x6b\ +\x1c\xb0\xa2\x13\xcc\x84\x9f\x2a\xe3\x9c\xf8\xd0\xf9\x94\x53\x25\ +\x90\x7b\xb6\x52\xb2\xd7\x73\xa7\x43\x9e\x70\xc3\x41\x46\x8a\x2c\ +\x25\x69\x4b\xb8\xd4\x13\xea\x49\xd3\xf1\x68\xdf\xf4\x71\x4b\xe9\ +\xfe\xef\xbe\x2c\x7a\x67\x55\x57\x7d\x66\x9d\x54\xac\xa5\xc9\x21\ +\x75\x94\x2d\x70\x29\xe1\x43\x9b\x04\x0c\xa1\xa6\x3d\xd4\x0c\x8c\ +\x23\x8a\x72\x9c\x81\x91\xab\x72\xb6\xb7\xe3\xa5\x1b\x06\xc5\x85\ +\x12\x87\xbd\x36\x6d\x3a\x89\x09\xa0\xc4\x40\xbb\x81\x84\x24\x01\ +\xf4\xc2\x94\x14\x49\x39\x24\xe3\xb9\xc9\xd2\x6e\xdf\xa7\xcd\xb5\ +\x79\xd9\x53\xee\x2b\x32\xab\x2a\x45\xca\xe4\x15\x06\x9b\xad\x4b\ +\x49\x8f\x35\x58\x1c\x5b\x51\xe3\xfa\x40\x90\x9f\x98\x64\x0c\x0e\ +\xc4\x0d\x18\x76\xf3\xa3\xfb\xa3\x69\x2d\x8a\x5d\xe3\xb6\xb6\x95\ +\xa1\x2a\xec\xf6\x58\x15\x9a\x45\x79\x29\x54\x09\x25\x44\x7b\xc0\ +\x39\xed\x28\xb6\xb4\xe4\xf1\x71\xa4\xb6\x17\xc7\xe6\x49\xce\x9a\ +\xfb\x3f\x4b\x74\xb3\x95\x35\x30\x24\x80\x4a\xdc\x92\x47\x01\xc4\ +\x8d\x48\x0c\xfb\x3b\x08\xdf\x75\x16\xca\xac\x38\x26\x90\x37\x4b\ +\x04\x8c\x5b\x9e\x40\xe8\x1c\x16\x85\x03\xd6\x73\x64\xf7\x57\x73\ +\x2a\x48\xdc\xd9\x56\xaa\xad\x96\x5f\xae\xb3\x02\x90\x12\xe0\x2f\ +\xc7\x6d\x2a\x08\x72\x4b\xee\xa3\x92\x39\x3a\x8e\x4a\x0d\xa4\xa8\ +\x06\xd0\x33\x92\x70\x09\x9d\x1c\xfa\x4c\x52\xad\x3d\xbb\x60\xef\ +\x5c\x7a\x7d\xc5\x54\x94\x9f\x79\x75\x31\x11\x09\x4b\x8d\xa8\x65\ +\xb0\x94\x8f\x00\x20\x80\x3c\x67\xec\x3c\x69\xf0\xdc\x8d\x90\xb3\ +\x77\x46\xbf\x6b\xdc\x17\x1c\x8a\x93\x6b\xb4\xeb\x2a\xa9\xc0\x8b\ +\x06\x71\x69\x89\x2f\x14\x70\xe3\x21\xb0\x08\x79\x03\xcf\x13\x8e\ +\xe3\xf2\x35\x29\xf8\x06\x92\x9e\x0d\xa4\x00\x06\x00\xc6\x9a\x69\ +\x6c\x49\x95\x70\x9b\x51\x30\xe2\xc4\xcc\xfa\xf3\xe4\xcf\xa0\xe5\ +\x01\xd7\x73\x4f\x86\x4c\xb9\x61\x8e\xfe\xc0\x71\xd0\x3c\x28\x12\ +\xbd\x31\xb6\x26\x22\x84\x8a\x6d\xa4\xc4\x65\x0e\xe3\xd9\x4f\x1e\ +\xff\x00\xd1\xd2\x4d\xea\x0d\xb2\x75\xee\x98\xba\x95\xb2\xf7\x1e\ +\xd2\xa5\x3c\xe5\x3e\x2c\xc8\x4f\x70\x8c\x82\xb5\x3c\x86\x96\x0b\ +\x80\x24\x77\x2a\x09\x0b\x49\x03\x24\x82\x93\xf7\xd5\xc9\x3b\x4d\ +\x4a\xd4\x41\xc1\x1f\x8d\x0a\x7a\x87\xe9\x13\x6f\x7a\x8c\xa2\x7f\ +\x88\xba\xa4\x48\x61\x69\x1f\xa4\xfb\x00\x1e\x07\x39\x07\x07\xea\ +\x0f\x70\x46\x08\xfb\xea\xab\xcd\x91\x55\x74\x98\x69\xc0\x0b\x04\ +\x11\xb6\x86\x3a\x96\xb6\x52\x94\x53\x50\x4e\x13\xfa\x3b\x18\x89\ +\xec\x6e\xe3\x6d\xad\xd3\x75\x37\x64\x6d\xfd\x69\xda\xba\x85\x2c\ +\x4f\x7a\x6d\x3e\x32\x9c\x89\x15\xb5\x63\x83\x6e\xbc\x3e\x56\xdd\ +\x50\x3d\x9b\x3f\x37\x63\x9c\x63\x47\x8a\x6c\x15\x32\x12\x8c\x1e\ +\xe3\xeb\xa4\x8b\x6d\xba\x11\xb8\x3a\x74\xdf\x48\xb4\x49\xb7\x1d\ +\xf6\x29\xd7\x0a\xc3\x34\x3b\xdb\x6f\xa7\xa9\x85\xb0\xe0\xee\xa6\ +\x2a\x6c\xa8\x29\x21\x1c\x7b\xa5\xec\x29\x3d\x88\xec\x74\xdd\xed\ +\x3e\xce\x3b\xb6\x55\x29\xd3\x46\xe8\x5d\x95\xc6\x66\xb6\x90\x88\ +\x77\x15\x57\xe2\x91\x1d\x40\xe5\x4e\x20\xa8\x72\x0a\x57\xd7\xbe\ +\x3e\xc0\x6a\xfb\x54\xeb\x82\xc9\x44\xf9\x4c\xc4\x82\x41\x66\x1b\ +\x65\xa9\xfb\x8c\xb3\xca\x21\x5a\x8a\x60\x90\xa4\xad\xdc\x73\xcf\ +\x8e\x7b\x7a\xc7\xff\xd9\ +\x00\x00\x0c\x8d\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ +\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0e\x74\x45\ +\x58\x74\x54\x69\x74\x6c\x65\x00\x43\x6f\x6d\x70\x75\x74\x65\x72\ +\xf8\x18\x12\x76\x00\x00\x00\x17\x74\x45\x58\x74\x41\x75\x74\x68\ +\x6f\x72\x00\x4c\x61\x70\x6f\x20\x43\x61\x6c\x61\x6d\x61\x6e\x64\ +\x72\x65\x69\xdf\x91\x1a\x2a\x00\x00\x0b\xcd\x49\x44\x41\x54\x68\ +\xde\xed\x9a\x7b\x70\x54\xd7\x7d\xc7\x3f\xbf\x73\xee\xdd\x97\x84\ +\x24\x24\x4b\x18\x1c\x40\x08\xe3\xc7\xb8\xe1\x61\x6c\x40\xb8\x60\ +\x43\x13\xc7\x75\xc8\xb4\x33\x29\x1d\xa7\xaf\x74\x9c\x4c\xeb\xd4\ +\x1d\x7b\x86\x71\x1a\xa7\x8f\xc9\xb4\x49\xa7\xd3\xa9\xeb\xc9\x4c\ +\xa7\x2f\x27\x2e\xff\xb4\xa4\x8d\xc7\x75\xec\xb1\x51\x1d\x03\xb2\ +\xe3\xc7\xe0\x40\x30\xf8\x05\x75\x1d\x22\x30\x46\x12\x48\x42\x20\ +\xb4\xd2\xee\xde\x73\x7e\xfd\xe3\xdc\x5d\x09\x1b\x61\x40\x8c\x99\ +\x4c\x7b\x67\xf7\xbe\xf6\xee\xdd\xdf\xf7\xfc\x1e\x9f\x73\xce\xdd\ +\x48\x55\xf9\x79\x5e\x22\x7e\xce\x97\xff\xdb\x02\xba\x9e\x7b\xea\ +\xef\x50\xb9\x57\x10\xf1\xea\xf1\xde\xe3\xbd\xa2\x93\xf7\x9d\xc7\ +\xe3\x71\x4e\x51\xef\xf1\xea\xc2\x79\x9f\x5e\xa3\x8a\x73\x2e\x3d\ +\x56\xbc\x7a\xbc\x73\xb5\xfd\xf0\x59\x7a\xde\x7b\xbc\xf3\x38\xa7\ +\x5e\xc4\x7d\xf5\xcf\xfe\xe4\x2f\x1e\xbe\x68\x01\x5d\x5d\x5d\x0d\ +\x18\x7e\xff\x96\xd5\x6b\xc5\x1a\x8b\x18\x83\x20\x00\x88\x84\x55\ +\x38\x56\x54\x15\x55\x82\x30\x4d\x85\x79\x87\xab\x8a\xf0\x0e\xe7\ +\x3c\xea\x3d\x89\x77\x78\x97\x90\x38\x87\x4b\x12\x12\xe7\xf1\x3e\ +\x1c\x57\x45\x17\x8b\x45\xd3\xfd\x7c\xf7\x37\x81\x8b\x17\x50\x2a\ +\x95\x4c\xb6\x60\x7d\x71\xac\xcc\xa7\x7e\xf7\xaf\xc8\x64\x62\x44\ +\x64\x1a\xfe\x54\x34\x6c\xa6\xbe\x42\x41\x70\x3c\xf2\x8d\x8d\x78\ +\xa7\x66\xda\x21\xa4\xaa\xa8\x57\xf2\xb9\x0c\x9f\x58\xb4\x1c\x24\ +\xfc\x48\x68\xf5\x09\x5b\x54\xc3\x2a\xdd\x30\xfb\x8a\x7a\x50\xf0\ +\xde\x91\x78\x48\x12\xc7\xd0\xc8\x18\xaa\x90\xcf\xc6\xcc\xbb\xb2\ +\x91\x6a\x75\x0c\x21\xa9\x78\x55\x2a\x49\xc2\x9e\x57\x5f\x26\x8e\ +\x33\x78\xf5\xd3\xcf\x01\xef\x3d\x1e\x30\xd6\x62\xac\x45\x44\xaa\ +\xed\x98\x86\x4c\x2a\x22\x35\x46\x01\x6b\x84\x38\x8e\x70\x5e\xc1\ +\x09\xd6\x28\xc5\x92\xc3\xd8\x18\x05\x5a\x9b\xeb\x31\x62\xf0\x12\ +\x1a\x41\x0c\x88\x2a\x82\xe2\xbd\x62\xad\x04\x01\xfe\x92\x08\x08\ +\xb1\x5c\xb3\x51\x74\x4a\xc3\xab\x79\x90\xcb\x58\x9c\x0f\x9e\x73\ +\xaa\x38\xa7\x8c\x95\x12\x54\x95\x6c\x6c\x89\x23\x43\x29\x71\xa0\ +\xd4\x3c\x06\x9a\x26\x7b\x30\x3a\x13\xc7\x97\x42\xc0\x30\x49\x52\ +\x4f\xb9\x52\x01\x0d\xc6\x88\x9b\xda\x70\x4d\x2d\xb2\x22\xf8\xc4\ +\xe1\xbc\xe2\xbc\x32\x56\xae\x04\x6f\xa0\x34\xd4\x65\xa8\x54\x3c\ +\x9a\x86\x60\x35\x06\x35\xf5\x6b\x92\x04\xa3\xe3\x4b\x23\x00\x9c\ +\x4b\xa8\x94\x2b\xa1\xba\x24\x09\xd5\x1c\x3e\x9b\xe1\xd5\x8d\x11\ +\xa5\x9c\x24\x69\x99\x84\xe2\x78\x05\xef\x95\x4c\x6c\x30\xc6\x50\ +\x71\x1e\xf5\x3a\x91\x3f\xd5\x7c\x52\xc5\x25\xc1\xdb\x71\x74\x09\ +\x04\x0c\x03\x51\xa9\xc4\xc8\xc8\x29\x5c\xa5\x4c\xa5\x3c\x86\x98\ +\x90\xc5\x35\xc3\x11\x7e\x65\xcd\xd5\xdc\xb1\x72\x01\x8f\x77\xbf\ +\xc3\xf6\x3d\x87\xf0\xce\xe0\x2a\x70\xfb\xca\x76\x6e\x5b\x36\x97\ +\xef\x77\xff\x0f\x3b\xf6\xf4\x52\xc8\x65\x48\x12\xc7\xa7\x6f\xfa\ +\x04\xb7\x2e\x99\xcd\x33\x3b\x0f\xf3\xd2\x1b\xfd\x78\xad\xb9\xa1\ +\xe6\x01\x1b\xd9\x5a\xe8\x46\xd3\x88\x20\x46\x93\xd3\x72\x7c\xe0\ +\x38\x49\xa9\x48\xa9\x78\x0a\x6b\x04\x90\xea\x0b\x10\x36\xac\xee\ +\x60\x46\x21\xcb\x1d\xab\xe6\xb3\x63\xd7\x7f\x53\x2e\x3a\x54\xe1\ +\x53\xcb\xe7\x51\x5f\xc8\xf0\xd9\x55\xf3\x79\xf1\xf5\x5e\x32\x56\ +\x50\x31\xfc\xd2\x8d\x73\xa8\xcb\xc5\xac\x5b\x36\x87\x17\xf6\xf5\ +\x05\x88\xe1\x71\x1e\x7c\x55\x80\x8d\x26\x3c\x70\xb1\x34\x35\xb1\ +\x92\xb3\x39\xfa\x7a\x0f\x71\xdf\x17\x6e\x9c\x92\xa6\x8f\x3f\xbe\ +\xa5\x46\xd3\xdb\xda\x27\x68\xba\x79\xf3\x23\x2c\x5d\x76\x13\x6f\ +\xf5\x45\xe4\x23\x50\x09\x82\x9f\xdd\xd5\xcb\xda\xc5\xb3\x78\xee\ +\x27\xbd\x20\x82\x8a\x84\x92\xaa\x4a\x9a\xc3\x58\x63\xd2\xbc\x81\ +\x08\x7f\xd9\x68\xca\xf6\x1d\xdd\xfc\xb8\x6f\x16\x33\x1a\x9b\x51\ +\x2f\xd8\x58\xe8\xde\xd7\xcf\x8e\x7d\x7d\x38\xef\x11\xb1\x60\x3c\ +\xea\x4c\xc8\x85\x2a\x27\x45\xd0\x5a\x0e\x88\x5c\x3e\x9a\x7a\xc5\ +\x8a\x47\xd5\x83\x01\xd4\x20\xc6\xa0\x5e\x53\xa6\x78\x14\x13\xbe\ +\x68\x3c\xd5\xc0\x74\xce\x4f\x84\xd0\xe5\xa6\xa9\xa4\xe1\x61\x30\ +\x60\x6c\x30\xd2\x00\xde\xa3\x22\xe0\x5d\x38\xa7\x02\x18\x04\x70\ +\x3e\x99\x10\x70\xb9\x69\x6a\xa2\x08\x1b\x59\x8c\x35\x98\xc8\x04\ +\x23\x7d\xf5\xb7\x3c\x60\xc1\x80\x3a\x41\x7c\x12\x3c\x50\x71\x93\ +\x05\x5c\x5e\x9a\x5a\x63\x30\x26\xc2\x9a\x08\x63\x2d\xaa\x92\xf2\ +\x02\x14\x41\xd5\x61\xbc\x45\xc5\x85\x46\xf1\x4a\x25\xa9\x4c\x08\ +\x70\xce\x93\xb8\x20\xc0\x03\xe2\x3f\x9a\xa6\x91\x31\xa1\x5f\x5e\ +\xa5\x69\xe9\xe2\x68\xea\x9c\x63\xff\x6b\x2f\x11\xe7\xea\x89\xb2\ +\x05\xac\x8d\x11\x63\xcf\x48\x98\x89\x5b\x84\xbd\xf1\x72\x85\x72\ +\xa5\x8c\xaf\x56\x21\xef\x1d\x3e\x6d\x15\xf5\x5a\xcd\x93\x73\xd2\ +\xd4\x5a\x43\xe2\x43\x38\x04\x01\xa1\xf5\xe3\xe8\xc2\x68\x2a\x02\ +\x5f\xfb\xd2\xed\xb4\xb4\xb4\xd0\xd8\xd4\x44\x36\x9b\xc1\x18\x13\ +\xf2\x25\xad\x70\xa1\xb2\x39\x5c\xfa\x56\xef\xa9\x54\xca\x67\xe6\ +\x80\xf3\xc1\xdd\x5e\x15\x93\xd6\xdd\xaa\xe1\x9f\xeb\x6c\xe7\xf6\ +\x9b\xae\xe2\x3f\x5f\xec\xe1\xf9\xbd\xbd\x88\x28\xd6\x1a\xd6\x2f\ +\x9d\xcd\x9a\xc5\x57\xf2\xf4\xce\xc3\x3c\xb3\xf3\x30\x22\x86\xba\ +\x7c\x86\x75\x4b\x67\x4f\x90\xf4\xcd\xfe\xda\x7d\xcf\x46\x53\xbc\ +\x67\x46\x5d\x81\xc6\x86\x3a\x66\x36\xd6\x93\xcf\xe7\x31\x46\x50\ +\x0d\xde\xf5\xde\xa7\x64\xf7\xbc\xbd\xff\x00\xbb\x77\xef\x66\xfd\ +\xfa\xf5\x14\x47\x47\x71\xce\x69\x1a\x42\x09\xce\x25\x35\xaf\x85\ +\xf2\x6f\xc0\x80\x88\x70\xe7\xca\xb9\xd4\xe7\x63\xee\x58\x31\x97\ +\x97\xde\x3a\x46\x1c\x59\x8c\xb1\xac\x5b\x36\x87\xba\x5c\xc4\xfa\ +\xa5\x73\xf8\xe1\xee\xa3\x18\x23\x64\xe3\xa8\x46\xd2\xf5\xcb\xe6\ +\xf0\xa3\x37\xfa\x83\x27\xa6\xa0\x69\x92\x24\x3c\xf9\xe4\x13\x14\ +\xea\xeb\xc8\x17\xf2\xc4\x51\x34\x16\x5a\x3f\xc0\x70\x32\x18\x8b\ +\xa3\xa3\x99\x7c\xbe\x20\xff\xfe\xbd\x2d\x1e\x11\x8f\xf2\xf7\xb5\ +\x24\xae\x0a\x88\xa2\x08\x63\x52\x78\xa5\x02\xba\x76\x1d\xe5\xb6\ +\x25\x57\xb2\x6d\x4f\x2f\x99\x28\x26\x93\x89\x30\xc6\xb0\xfd\xb5\ +\x7e\x6e\xb9\xa1\x95\x67\xf7\xf4\x12\xc7\x11\xf9\x4c\x04\x16\x9e\ +\xdd\xdd\xc7\xda\x4f\xb6\xf1\xdc\x9e\x5e\xc2\x4d\xfc\x94\x34\x55\ +\x85\xc5\x4b\x17\xd3\x36\xeb\x4a\x0e\x1d\xea\x29\xbd\xfd\xc6\x81\ +\x6b\x0b\x85\x82\xcb\xe5\x72\x1e\x8a\x14\x81\xb0\x02\x30\x14\x4f\ +\x8f\x03\x63\x94\x4a\x51\xb2\x65\xcb\x96\x81\x5a\x08\x55\x93\x38\ +\xb2\x06\x63\x2c\x22\x20\x46\x10\x11\x5e\x78\xfd\x18\x2f\xbc\x7e\ +\x1c\x24\x08\xcc\xc6\x11\x62\x85\x97\xde\x1e\xa0\x7b\xdf\x31\x4a\ +\x89\x23\x97\xc9\x10\xc7\x16\xef\x95\x17\xde\xe8\xa7\x7b\x5f\x3f\ +\xaa\x3e\x74\xee\xfc\xd4\x34\x05\xe5\x93\x37\x2c\x61\xde\xbc\xf9\ +\xb4\xb5\xb6\x68\x1c\x47\xab\xbf\xfe\x47\x7f\xfa\x0c\x30\x0e\x38\ +\x3d\x8f\x49\xab\xc8\x6b\x5a\x76\x24\xb8\xd5\x5a\x53\x33\x3e\x74\ +\x4f\x0c\x46\x26\xba\x17\x71\x1c\xd7\x20\x57\xb1\x10\x23\x44\xb6\ +\xfa\x79\xe0\x82\x18\x45\x1d\x1f\x49\x53\xe7\x1c\xef\xbe\xfb\x53\ +\x46\x8b\x45\xea\xea\xeb\x72\x73\x66\xcf\xf9\xb7\x7f\x7e\xe4\x1f\ +\x7a\x72\xb9\xec\xfe\xc6\xe6\xa6\xe8\xbf\x7e\xf8\x74\x9d\x31\x26\ +\x16\xc1\xfa\xd0\xf1\x4c\x50\x1d\xf5\xea\x7f\xa4\x95\xf1\x7f\xbc\ +\xf3\xce\x8d\x7d\x91\x77\xbe\x5a\x31\xb1\x91\x0d\x75\x59\x04\xb1\ +\x21\x94\x44\x0c\xc6\x84\xec\x10\x91\x34\xf9\x14\x07\xc4\x2a\x60\ +\x43\xb9\xf3\x1a\xcc\x74\x46\xc1\xfb\x50\xc8\xcf\x83\xa6\x99\x9c\ +\x45\x8c\x23\x93\xb5\xac\x58\xb1\xc2\x7a\x47\xc7\xa3\xff\xf2\xdd\ +\xd8\x2b\xf7\xcc\xbb\x6a\xf6\x60\x6b\x6b\x6b\x12\xc7\x19\x1b\xa2\ +\xce\x44\x26\x63\x67\x16\xf2\xf9\xbb\x6c\xa6\xb0\x05\x58\x1f\x79\ +\xef\x6b\xe4\xb5\xc6\x60\x6d\x28\x85\x22\xc1\x78\x9b\x7a\x03\x31\ +\x18\x23\x18\x93\xa2\x5e\x15\x23\x16\x9f\x96\x5b\xa3\x1e\xef\x14\ +\x8c\x22\x5e\x90\x6a\x69\x3e\x07\x4d\x93\x24\xe1\x07\x3f\x78\x32\ +\x89\xe3\x58\xad\xb5\x18\x63\x00\x31\x23\x23\x23\xbb\x7e\xbc\x73\ +\xd7\x7b\x9d\x9d\x9d\x23\x5f\xbe\xe7\x8b\xdf\x88\x6c\xe6\x36\x41\ +\xf2\xa0\x39\x15\x9c\xf7\x3e\x36\x46\x9e\x16\x11\x89\x6a\xa5\xca\ +\x6b\xe8\x7d\x8a\x99\x64\x6c\x08\x27\x23\x26\x88\xb1\xb5\x3a\x85\ +\x45\x50\x0b\x96\x60\xb8\x53\x8b\xe0\x21\x9d\x2d\x50\x95\xf3\xa1\ +\x69\xf9\x9d\x03\xef\x76\x4e\xee\xeb\x39\xe7\xdc\x89\x13\x27\x4e\ +\xc5\x71\x3c\xfe\xdb\x5f\xfc\x8d\xfb\x0c\x66\xf6\xf1\xbe\xfe\x5f\ +\x1f\x1c\x3c\x75\x72\xa0\x38\x30\xd6\x10\xc5\x76\xe6\xcc\xc6\xd2\ +\xf3\xcf\xef\x3a\x1e\x72\x20\xed\xe2\x0e\x9f\x3a\xcd\xfe\x5d\x3b\ +\x6a\xc3\xc2\xb4\x17\x3d\xfd\xe5\x1c\x34\x55\x25\x73\xf3\xaa\x15\ +\x3f\x99\x35\xab\x0d\x9f\x24\xa5\x7c\x7d\x5d\x76\x68\xe8\x04\x57\ +\x34\x37\x53\x71\xe5\xf1\xb7\x0f\x1c\xc8\x0e\x0f\x9e\xd4\xa6\x96\ +\xc6\x4f\x97\xc6\xc6\xcb\x71\x6c\x5f\x79\xf9\xd5\x3d\x9f\xed\xea\ +\xea\x4a\x00\xaf\xaa\x1a\x79\xef\xc9\xe7\x23\x36\x7f\xeb\x0b\x58\ +\x9b\xe6\x80\xb1\xe9\xdb\x9c\xf1\xae\x26\xb6\xd4\x4c\x49\x6b\x76\ +\x3a\x1e\x08\xfb\x2e\x3d\xf6\xe7\x45\xd3\xd6\x2b\x9a\x29\x97\x4a\ +\x2c\x5f\xbe\x3c\xfa\xe9\xc1\x77\xb5\xad\xa5\x05\x8c\x70\xdd\xc2\ +\xeb\xed\xc1\x9f\x1d\x94\x05\x0b\xe7\xcb\xc0\xc0\x20\x0b\x16\xb4\ +\xe7\xfa\xfb\x8e\xcd\xbf\x61\xf1\xf5\xbf\xb3\x75\xeb\xd6\xef\xd4\ +\xaa\x50\x52\x49\x8c\xf7\x9e\x96\xa6\xc6\xd0\x23\x34\x82\x15\x8b\ +\x58\x8b\x11\xa9\x19\x3e\x91\x17\x32\x21\x40\xd3\x6e\xf2\xa4\x11\ +\x5c\xb5\x1b\x10\x8c\x3f\x37\x4d\xbd\xf7\x18\x11\xda\xdb\xe7\x53\ +\x1c\x3d\x6d\x97\x2d\x59\xca\xa1\xc3\x87\x69\x9b\xd5\x46\x3e\x9f\ +\x8f\xe7\xcd\x9b\xcb\xc8\xc9\x53\x74\x2c\x68\x67\x70\x70\x50\xd6\ +\xac\x59\x93\x7f\xea\xa9\x27\xff\x7c\xd3\xa6\x4d\xff\xfa\xf0\xc3\ +\x0f\x8f\xa5\x63\x62\x79\x6a\xdb\xf6\xed\x1b\x40\x3d\x67\x50\x70\ +\xb2\x71\x0a\x29\x4d\x13\x97\xe4\xc3\x38\xc0\xa7\x93\xae\xd5\x61\ +\xa6\xe2\x52\xc3\xd5\x9f\x9d\xa6\x63\xc5\x22\xf9\x7c\x81\xef\x6d\ +\xd9\x82\x18\x09\x63\x88\xa1\x21\x46\x47\x8b\xac\xee\x5c\xcd\xc8\ +\xe9\x51\x7a\x8f\x1e\xe5\xc4\x89\x21\xae\xbd\xe6\x3a\x8e\xf7\x1f\ +\xa7\xb7\xb7\x97\x9e\x9e\x1e\xbc\x2a\x9f\xf9\xcc\x9d\x57\x75\xae\ +\xbe\xe5\xcd\xee\xee\x6d\xf7\x01\x7f\x0d\x10\xfd\xe1\xbd\xf7\xff\ +\xda\x43\x0f\x3d\xd4\xa6\xaa\x69\xb1\x2e\x4e\xc0\x2f\xdd\x29\x97\ +\xcb\x92\x24\xa7\xe4\xe4\x78\xa5\xae\x7d\xee\xbc\xfd\xab\x57\x75\ +\xda\xe2\x58\xb1\xd6\x55\x13\x11\xe2\x4c\x8e\x57\x77\xee\xa4\xad\ +\x65\x16\x0d\x0d\x8d\x34\x34\xcc\x20\x97\xcb\x11\x65\x62\x4c\x5a\ +\x82\x43\x81\x00\x23\x82\xb5\x11\x4d\x4d\x4d\xe4\x72\x39\x0a\x85\ +\x02\xd9\x6c\x36\x44\x80\xc8\xa4\x51\x5d\x3a\x94\xad\x1e\x0b\x74\ +\x2c\xe8\x98\xdd\xd5\xb5\xf5\x9e\x9a\x00\x80\x07\x1e\x78\xe0\xd8\ +\x54\x39\x28\x61\x8c\x19\x01\xf9\xbf\xfd\xf6\xdf\x7c\xae\x73\xe5\ +\xaa\xf2\xcd\x37\xad\xcc\x97\x4a\xa5\x33\xae\xcb\x66\x73\xf8\xa4\ +\xc2\x7f\x7c\xff\xb1\xf1\x81\x81\x01\x97\xcd\x66\x7d\x1c\xc5\x69\ +\x69\x04\x11\x39\x4b\x49\x90\x40\x7d\x91\x40\xed\xf3\x58\xca\xa5\ +\x72\x36\xa9\x24\xdd\xb5\x1c\x48\x0d\x9c\xea\xdb\x06\xc8\xac\x5a\ +\xbb\x6a\xfe\x5d\x9f\xff\xfc\x37\x5b\x5a\xaf\xd8\x60\x24\xca\xbe\ +\xb6\x77\x2f\xe5\x72\xf9\x03\x02\xb2\x14\x0a\x0d\xdc\xba\x76\x6d\ +\x5c\x1c\x1d\xed\x8d\xe3\xf8\x9d\x86\x99\x4d\x51\x2e\x9b\x29\x18\ +\x63\x62\x63\xc4\x68\x4a\x53\x75\x5a\x2c\x55\x4a\xaf\xbc\x7f\xe8\ +\xf0\xa3\xdb\xb6\xbd\x78\xec\x42\x0b\xdb\x13\x4f\x3c\x31\x3c\xf9\ +\x01\x87\xd9\xb8\x71\xa3\x3d\x72\xe4\x88\x3d\x43\x69\xb9\x2c\xc7\ +\x8f\x1f\xcf\x0e\x0f\x0f\xb7\x74\xde\xbc\x72\xeb\x86\x0d\xbf\x3a\ +\x5f\xc5\xcb\xc9\x93\x27\x00\x4f\x2e\xfa\xe0\x94\x92\x23\x6b\x2d\ +\x9d\x9d\x9d\xb6\x5c\x4a\xda\xbf\xfb\xe8\x77\xec\x47\xd1\xf4\xea\ +\x6b\xaf\xfd\xa7\x7b\xef\xdd\xb4\x7e\x5a\x4f\x68\x96\x2f\x5f\x6e\ +\x0e\x1e\x3c\x18\xe5\x72\x39\xfb\xc1\xd6\x17\x91\x9c\x31\xa6\xfe\ +\xc0\xfe\x03\x6f\x7e\xfd\x8f\x1f\x9c\x9b\x0e\xda\xce\xa7\xf4\x5f\ +\x10\x4d\x75\x1a\x4f\x1a\xa3\x8e\x8e\x0e\x7f\xe4\xc8\x11\x1d\x1e\ +\x1e\x3e\xe3\x26\x49\x92\x78\xe7\x5c\x29\x8a\xa2\x63\xaf\xbc\xbc\ +\xf3\xbe\xe6\xe6\xe6\x19\xd6\x5a\x7b\x8e\x70\xbb\x68\x9a\x4e\xcb\ +\x03\x8f\x3d\xf6\x98\x07\xca\x0f\x3e\xf8\xe0\x02\x1b\xb1\x6d\xe4\ +\xf4\xe9\xd9\x63\xa5\x72\xa6\xa5\xa5\x99\xc1\xc1\x21\x2e\x64\x5b\ +\x1a\x1b\x2b\x65\xf3\xf9\xec\xe0\xe0\xd0\x05\xd1\x74\x5a\x02\xd2\ +\x1b\xb8\xaf\x7e\x6d\xd3\x5f\xce\xbd\xaa\xa3\xa7\xff\x58\xff\xbc\ +\xf6\xa6\x26\x06\x06\x06\xb9\xfe\xba\x6b\x2e\x68\xbb\x62\xc5\xca\ +\xe8\xad\xfd\x6f\x9e\x9b\xa6\xfd\xfd\x1f\xa2\xe9\xb4\x9f\x52\xde\ +\x7f\xff\x57\x7e\x21\x97\xaf\x5f\xbf\xb0\x63\x61\x21\x9b\xcd\xc8\ +\x89\xa1\x13\x55\xfa\xd1\x30\xa3\x9e\x4a\xb9\x7c\xd6\xad\xab\x24\ +\x74\x2c\x68\xa7\x7a\xfd\xe9\x91\x53\xf6\xc6\x25\xcb\xf8\x59\x4f\ +\x0f\xad\x6d\xad\x67\xa7\xe9\x2f\xae\xfd\x10\x4d\xa7\x2d\xa0\x54\ +\x4e\xbe\xf5\xa5\x2f\xff\xd6\xe1\xa3\xbd\x47\x6f\xdc\xfb\xda\x5e\ +\x3c\x9e\x9e\xc3\x87\x08\x95\x3b\x4c\x74\x55\x67\x9b\x27\xcf\x3c\ +\x03\x1c\x39\x72\x04\x55\xe5\xbd\xf7\x8e\xb0\x72\xc5\x4a\x4e\x8d\ +\x9c\xe6\xe8\xfb\xef\x33\x34\x34\x38\x05\x4d\x7f\xf9\x43\x34\x9d\ +\x96\x80\xbb\xef\xbe\xbb\x35\x9b\xcf\xac\x5b\xb4\xe8\x9a\xfa\x45\ +\x57\x2f\x62\xed\x9a\x5b\x6b\x06\x8a\x9c\x69\x74\x15\x19\xb5\xf3\ +\xd5\x91\xe1\xa4\xbc\x56\x14\xee\xfa\xcd\x49\xf3\xa4\x1f\xa6\xe9\ +\xc2\x05\x0b\xcf\xa0\xe9\x74\x73\x60\x64\x78\xf8\x44\xdf\x57\xfe\ +\xe0\xf7\x66\xd9\x28\x12\x3e\x86\xa5\x52\xae\x64\x2b\xe5\x4a\xf7\ +\x25\xc9\x81\xcd\x9b\x37\x8f\xaf\x5b\xb7\xee\x86\xa6\xa6\xa6\xfa\ +\x8f\xf3\x2f\x02\x93\x69\x3a\xed\x1c\xe8\xee\xee\x4e\xd2\xa7\x46\ +\xff\xff\x67\x8f\x8f\x7b\xf9\x5f\x5a\xf1\x31\x65\xff\xe0\x15\x90\ +\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +" + +qt_resource_name = "\ +\x00\x06\ +\x07\x03\x7d\xc3\ +\x00\x69\ +\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ +\x00\x0e\ +\x05\xcd\xf4\xe7\ +\x00\x63\ +\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x12\ +\x04\xe4\x91\x47\ +\x00\x63\ +\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\ +\x00\x67\ +\x00\x0c\ +\x07\x11\x5c\xc7\ +\x00\x6c\ +\x00\x65\x00\x61\x00\x70\x00\x66\x00\x72\x00\x6f\x00\x67\x00\x2e\x00\x6a\x00\x70\x00\x67\ +\x00\x13\ +\x0d\x76\x37\xc7\ +\x00\x63\ +\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\ +\x00\x6e\x00\x67\ +" + +qt_resource_struct = "\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\ +\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xf7\ +\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x19\xd2\ +\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x20\xbd\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/leap/tests/fakeclient.py b/src/leap/tests/fakeclient.py new file mode 100644 index 00000000..45de2cd6 --- /dev/null +++ b/src/leap/tests/fakeclient.py @@ -0,0 +1,63 @@ +fakeoutput = """ +mullvad Sun Jun 17 14:34:57 2012 OpenVPN 2.2.1 i486-linux-gnu [SSL] [LZO2] [EPOLL] [PKCS11] [eurephia] [MH] [PF_INET6] [IPv6 payload 20110424-2 (2.2RC2)] built + on Mar 23 2012 +Sun Jun 17 14:34:57 2012 MANAGEMENT: TCP Socket listening on [AF_INET]127.0.0.1:7505 +Sun Jun 17 14:34:57 2012 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts +Sun Jun 17 14:34:57 2012 WARNING: file 'ssl/1021380964266.key' is group or others accessible +Sun Jun 17 14:34:57 2012 LZO compression initialized +Sun Jun 17 14:34:57 2012 Control Channel MTU parms [ L:1542 D:138 EF:38 EB:0 ET:0 EL:0 ] +Sun Jun 17 14:34:57 2012 Socket Buffers: R=[163840->131072] S=[163840->131072] +Sun Jun 17 14:34:57 2012 Data Channel MTU parms [ L:1542 D:1450 EF:42 EB:135 ET:0 EL:0 AF:3/1 ] +Sun Jun 17 14:34:57 2012 Local Options hash (VER=V4): '41690919' +Sun Jun 17 14:34:57 2012 Expected Remote Options hash (VER=V4): '530fdded' +Sun Jun 17 14:34:57 2012 UDPv4 link local: [undef] +Sun Jun 17 14:34:57 2012 UDPv4 link remote: [AF_INET]46.21.99.25:1197 +Sun Jun 17 14:34:57 2012 TLS: Initial packet from [AF_INET]46.21.99.25:1197, sid=63c29ace 1d3060d0 +Sun Jun 17 14:34:58 2012 VERIFY OK: depth=2, /C=NA/ST=None/L=None/O=Mullvad/CN=Mullvad_CA/emailAddress=info@mullvad.net +Sun Jun 17 14:34:58 2012 VERIFY OK: depth=1, /C=NA/ST=None/L=None/O=Mullvad/CN=master.mullvad.net/emailAddress=info@mullvad.net +Sun Jun 17 14:34:58 2012 Validating certificate key usage +Sun Jun 17 14:34:58 2012 ++ Certificate has key usage 00a0, expects 00a0 +Sun Jun 17 14:34:58 2012 VERIFY KU OK +Sun Jun 17 14:34:58 2012 Validating certificate extended key usage +Sun Jun 17 14:34:58 2012 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication +Sun Jun 17 14:34:58 2012 VERIFY EKU OK +Sun Jun 17 14:34:58 2012 VERIFY OK: depth=0, /C=NA/ST=None/L=None/O=Mullvad/CN=se2.mullvad.net/emailAddress=info@mullvad.net +Sun Jun 17 14:34:59 2012 Data Channel Encrypt: Cipher 'BF-CBC' initialized with 128 bit key +Sun Jun 17 14:34:59 2012 Data Channel Encrypt: Using 160 bit message hash 'SHA1' for HMAC authentication +Sun Jun 17 14:34:59 2012 Data Channel Decrypt: Cipher 'BF-CBC' initialized with 128 bit key +Sun Jun 17 14:34:59 2012 Data Channel Decrypt: Using 160 bit message hash 'SHA1' for HMAC authentication +Sun Jun 17 14:34:59 2012 Control Channel: TLSv1, cipher TLSv1/SSLv3 DHE-RSA-AES256-SHA, 2048 bit RSA +Sun Jun 17 14:34:59 2012 [se2.mullvad.net] Peer Connection Initiated with [AF_INET]46.21.99.25:1197 +Sun Jun 17 14:35:01 2012 SENT CONTROL [se2.mullvad.net]: 'PUSH_REQUEST' (status=1) +Sun Jun 17 14:35:02 2012 PUSH: Received control message: 'PUSH_REPLY,redirect-gateway def1 bypass-dhcp,dhcp-option DNS 10.11.0.1,route 10.11.0.1,topology net30,ifconfig 10.11.0.202 10.11.0.201' +Sun Jun 17 14:35:02 2012 OPTIONS IMPORT: --ifconfig/up options modified +Sun Jun 17 14:35:02 2012 OPTIONS IMPORT: route options modified +Sun Jun 17 14:35:02 2012 OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified +Sun Jun 17 14:35:02 2012 ROUTE default_gateway=192.168.0.1 +Sun Jun 17 14:35:02 2012 TUN/TAP device tun0 opened +Sun Jun 17 14:35:02 2012 TUN/TAP TX queue length set to 100 +Sun Jun 17 14:35:02 2012 do_ifconfig, tt->ipv6=0, tt->did_ifconfig_ipv6_setup=0 +Sun Jun 17 14:35:02 2012 /sbin/ifconfig tun0 10.11.0.202 pointopoint 10.11.0.201 mtu 1500 +Sun Jun 17 14:35:02 2012 /etc/openvpn/update-resolv-conf tun0 1500 1542 10.11.0.202 10.11.0.201 init +dhcp-option DNS 10.11.0.1 +Sun Jun 17 14:35:05 2012 /sbin/route add -net 46.21.99.25 netmask 255.255.255.255 gw 192.168.0.1 +Sun Jun 17 14:35:05 2012 /sbin/route add -net 0.0.0.0 netmask 128.0.0.0 gw 10.11.0.201 +Sun Jun 17 14:35:05 2012 /sbin/route add -net 128.0.0.0 netmask 128.0.0.0 gw 10.11.0.201 +Sun Jun 17 14:35:05 2012 /sbin/route add -net 10.11.0.1 netmask 255.255.255.255 gw 10.11.0.201 +Sun Jun 17 14:35:05 2012 Initialization Sequence Completed +Sun Jun 17 14:34:57 2012 MANAGEMENT: TCP Socket listening on [AF_INET]127.0.0.1:7505 +""" + +import time +import sys + + +def write_output(): + for line in fakeoutput.split('\n'): + sys.stdout.write(line + '\n') + sys.stdout.flush() + #print(line) + time.sleep(0.1) + +if __name__ == "__main__": + write_output() diff --git a/src/leap/tests/mocks/__init__.py b/src/leap/tests/mocks/__init__.py new file mode 100644 index 00000000..06f96870 --- /dev/null +++ b/src/leap/tests/mocks/__init__.py @@ -0,0 +1 @@ +import manager diff --git a/src/leap/tests/mocks/manager.py b/src/leap/tests/mocks/manager.py new file mode 100644 index 00000000..564631cd --- /dev/null +++ b/src/leap/tests/mocks/manager.py @@ -0,0 +1,20 @@ +from mock import Mock + +from eip_client.vpnmanager import OpenVPNManager + +vpn_commands = { + 'status': [ + 'OpenVPN STATISTICS', 'Updated,Mon Jun 25 11:51:21 2012', + 'TUN/TAP read bytes,306170', 'TUN/TAP write bytes,872102', + 'TCP/UDP read bytes,986177', 'TCP/UDP write bytes,439329', + 'Auth read bytes,872102'], + 'state': ['1340616463,CONNECTED,SUCCESS,172.28.0.2,198.252.153.38'], + # XXX add more tests + } + + +def get_openvpn_manager_mocks(): + manager = OpenVPNManager() + manager.status = Mock(return_value='\n'.join(vpn_commands['status'])) + manager.state = Mock(return_value=vpn_commands['state'][0]) + return manager diff --git a/src/leap/utils/__init__.py b/src/leap/utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/utils/__init__.py diff --git a/src/leap/utils/coroutines.py b/src/leap/utils/coroutines.py new file mode 100644 index 00000000..5e25eb63 --- /dev/null +++ b/src/leap/utils/coroutines.py @@ -0,0 +1,107 @@ +# the problem of watching a stdout pipe from +# openvpn binary: using subprocess and coroutines +# acting as event consumers + +from __future__ import division, print_function + +from subprocess import PIPE, Popen +import sys +from threading import Thread + +ON_POSIX = 'posix' in sys.builtin_module_names + + +# +# Coroutines goodies +# + +def coroutine(func): + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + return start + + +@coroutine +def process_events(callback): + """ + coroutine loop that receives + events sent and dispatch the callback. + :param callback: callback to be called\ +for each event + :type callback: callable + """ + try: + while True: + m = (yield) + if callable(callback): + callback(m) + else: + #XXX log instead + print('not a callable passed') + except GeneratorExit: + return + +# +# Threads +# + + +def launch_thread(target, args): + """ + launch and demonize thread. + :param target: target function that will run in thread + :type target: function + :param args: args to be passed to thread + :type args: list + """ + t = Thread(target=target, + args=args) + t.daemon = True + t.start() + return t + + +def watch_output(out, observers): + """ + initializes dict of observer coroutines + and pushes lines to each of them as they are received + from the watched output. + :param out: stdout of a process. + :type out: fd + :param observers: tuple of coroutines to send data\ +for each event + :type ovservers: tuple + """ + observer_dict = {observer: process_events(observer) + for observer in observers} + for line in iter(out.readline, b''): + for obs in observer_dict: + observer_dict[obs].send(line) + out.close() + + +def spawn_and_watch_process(command, args, observers=None): + """ + spawns a subprocess with command, args, and launch + a watcher thread. + :param command: command to be executed in the subprocess + :type command: str + :param args: arguments + :type args: list + :param observers: tuple of observer functions to be called \ +for each line in the subprocess output. + :type observers: tuple + :return: a tuple containing the child process instance, and watcher_thread, + :rtype: (Subprocess, Thread) + """ + subp = Popen([command] + args, + stdout=PIPE, + stderr=PIPE, + bufsize=1, + close_fds=ON_POSIX) + watcher = launch_thread( + watch_output, + (subp.stdout, observers)) + return subp, watcher diff --git a/src/leap/utils/leap_argparse.py b/src/leap/utils/leap_argparse.py new file mode 100644 index 00000000..9c355134 --- /dev/null +++ b/src/leap/utils/leap_argparse.py @@ -0,0 +1,20 @@ +import argparse + + +def build_parser(): + epilog = "Copyright 2012 The Leap Project" + parser = argparse.ArgumentParser(description=""" +Launches main LEAP Client""", epilog=epilog) + parser.add_argument('--debug', action="store_true", + help='launches in debug mode') + parser.add_argument('--config', metavar="CONFIG FILE", nargs='?', + action="store", dest="config_file", + type=argparse.FileType('r'), + help='optional config file') + return parser + + +def init_leapc_args(): + parser = build_parser() + opts = parser.parse_args() + return parser, opts |