summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc23
-rw-r--r--.gitattributes1
-rw-r--r--.gitignore11
-rw-r--r--.tx/config10
-rw-r--r--CHANGES.txt1
-rw-r--r--MANIFEST.in5
-rw-r--r--Makefile25
-rw-r--r--NEWS.rst67
-rw-r--r--README.txt95
-rw-r--r--data/TODO1
-rw-r--r--data/images/leapfrog.jpgbin0 -> 1767 bytes
-rw-r--r--debian/README.Debian6
-rw-r--r--debian/README.source9
-rw-r--r--debian/changelog14
-rw-r--r--debian/compat1
-rw-r--r--debian/control47
-rw-r--r--debian/copyright17
-rw-r--r--debian/docs3
-rw-r--r--debian/init.d.ex154
-rw-r--r--debian/leap-client.doc-base.EX20
-rw-r--r--debian/leap-client.install1
-rw-r--r--debian/menu.ex2
-rw-r--r--debian/postinst.ex39
-rw-r--r--debian/postrm.ex37
-rw-r--r--debian/preinst.ex35
-rw-r--r--debian/prerm.ex38
-rwxr-xr-xdebian/rules65
-rw-r--r--debian/source/format1
-rw-r--r--debian/source/include-binaries1
-rw-r--r--debian/watch.ex10
-rw-r--r--ez_setup.py284
-rw-r--r--openvpn/README6
-rw-r--r--openvpn/Sources4
-rwxr-xr-xopenvpn/build.zsh191
-rw-r--r--pkg/requirements.pip2
-rwxr-xr-xrun_tests.sh23
-rw-r--r--setup.cfg3
-rwxr-xr-xsetup.py2
-rw-r--r--setup/install_venv.py242
-rw-r--r--setup/linux/leap.desktop13
-rw-r--r--setup/linux/polkit/net.openvpn.gui.leap.policy23
-rw-r--r--setup/requirements.pip1
-rwxr-xr-xsetup/scripts/leap6
-rw-r--r--setup/test-requires5
-rwxr-xr-xsetup/tools/with_venv.sh4
-rw-r--r--src/leap/__init__.py30
-rw-r--r--src/leap/app.py93
-rw-r--r--src/leap/base/__init__.py0
-rw-r--r--src/leap/base/auth.py355
-rw-r--r--src/leap/base/authentication.py11
-rw-r--r--src/leap/base/checks.py213
-rw-r--r--src/leap/base/config.py348
-rw-r--r--src/leap/base/connection.py115
-rw-r--r--src/leap/base/constants.py42
-rw-r--r--src/leap/base/exceptions.py97
-rw-r--r--src/leap/base/network.py107
-rw-r--r--src/leap/base/pluggableconfig.py455
-rw-r--r--src/leap/base/providers.py29
-rw-r--r--src/leap/base/specs.py67
-rw-r--r--src/leap/base/tests/__init__.py0
-rw-r--r--src/leap/base/tests/test_auth.py58
-rw-r--r--src/leap/base/tests/test_checks.py177
-rw-r--r--src/leap/base/tests/test_config.py247
-rw-r--r--src/leap/base/tests/test_providers.py150
-rw-r--r--src/leap/base/tests/test_validation.py92
-rw-r--r--src/leap/baseapp/constants.py6
-rw-r--r--src/leap/baseapp/dialogs.py50
-rw-r--r--src/leap/baseapp/eip.py243
-rw-r--r--src/leap/baseapp/leap_app.py153
-rw-r--r--src/leap/baseapp/log.py69
-rw-r--r--src/leap/baseapp/mainwindow.py596
-rw-r--r--src/leap/baseapp/network.py63
-rw-r--r--src/leap/baseapp/systray.py268
-rw-r--r--src/leap/certs/__init__.py7
-rw-r--r--src/leap/crypto/__init__.py0
-rw-r--r--src/leap/crypto/certs.py112
-rw-r--r--src/leap/crypto/certs_gnutls.py112
-rw-r--r--src/leap/crypto/leapkeyring.py70
-rw-r--r--src/leap/crypto/tests/__init__.py0
-rw-r--r--src/leap/crypto/tests/test_certs.py22
-rw-r--r--src/leap/eip/checks.py537
-rw-r--r--src/leap/eip/conductor.py340
-rw-r--r--src/leap/eip/config.py552
-rw-r--r--src/leap/eip/constants.py3
-rw-r--r--src/leap/eip/eipconnection.py405
-rw-r--r--src/leap/eip/exceptions.py175
-rw-r--r--src/leap/eip/openvpnconnection.py410
-rw-r--r--src/leap/eip/specs.py136
-rw-r--r--src/leap/eip/tests/__init__.py0
-rw-r--r--src/leap/eip/tests/data.py51
-rw-r--r--src/leap/eip/tests/test_checks.py373
-rw-r--r--src/leap/eip/tests/test_config.py298
-rw-r--r--src/leap/eip/tests/test_eipconnection.py216
-rw-r--r--src/leap/eip/tests/test_openvpnconnection.py161
-rw-r--r--src/leap/eip/udstelnet.py38
-rw-r--r--src/leap/eip/vpnmanager.py263
-rw-r--r--src/leap/eip/vpnwatcher.py169
-rw-r--r--src/leap/gui/__init__.py11
-rw-r--r--src/leap/gui/constants.py13
-rw-r--r--src/leap/gui/firstrun/__init__.py28
-rw-r--r--src/leap/gui/firstrun/connect.py214
-rw-r--r--src/leap/gui/firstrun/constants.py0
-rw-r--r--src/leap/gui/firstrun/intro.py68
-rw-r--r--src/leap/gui/firstrun/last.py119
-rw-r--r--src/leap/gui/firstrun/login.py332
-rw-r--r--src/leap/gui/firstrun/mixins.py18
-rw-r--r--src/leap/gui/firstrun/providerinfo.py106
-rw-r--r--src/leap/gui/firstrun/providerselect.py471
-rw-r--r--src/leap/gui/firstrun/providersetup.py157
-rw-r--r--src/leap/gui/firstrun/register.py387
-rwxr-xr-xsrc/leap/gui/firstrun/tests/integration/fake_provider.py302
-rwxr-xr-xsrc/leap/gui/firstrun/wizard.py309
-rw-r--r--src/leap/gui/locale_rc.py813
-rw-r--r--src/leap/gui/mainwindow_rc.py1799
-rw-r--r--src/leap/gui/progress.py488
-rw-r--r--src/leap/gui/styles.py16
-rw-r--r--src/leap/gui/test_mainwindow_rc.py (renamed from src/leap/gui/tests/test_mainwindow_rc.py)14
-rw-r--r--src/leap/gui/tests/__init__.py0
-rw-r--r--src/leap/gui/tests/integration/fake_user_signup.py84
-rw-r--r--src/leap/gui/tests/test_firstrun_login.py212
-rw-r--r--src/leap/gui/tests/test_firstrun_providerselect.py203
-rw-r--r--src/leap/gui/tests/test_firstrun_register.py244
-rw-r--r--src/leap/gui/tests/test_firstrun_wizard.py137
-rw-r--r--src/leap/gui/tests/test_progress.py449
-rw-r--r--src/leap/gui/tests/test_threads.py27
-rw-r--r--src/leap/gui/threads.py21
-rw-r--r--src/leap/gui/utils.py34
-rw-r--r--src/leap/testing/__init__.py0
-rw-r--r--src/leap/testing/basetest.py85
-rw-r--r--src/leap/testing/cacert.pem23
-rw-r--r--src/leap/testing/https_server.py68
-rw-r--r--src/leap/testing/leaptestscert.pem84
-rw-r--r--src/leap/testing/leaptestskey.pem27
-rw-r--r--src/leap/testing/pyqt.py52
-rw-r--r--src/leap/testing/qunittest.py302
-rw-r--r--src/leap/testing/test_basetest.py91
-rw-r--r--src/leap/util/__init__.py9
-rw-r--r--src/leap/util/certs.py18
-rw-r--r--src/leap/util/coroutines.py8
-rw-r--r--src/leap/util/dicts.py268
-rw-r--r--src/leap/util/fileutil.py11
-rw-r--r--src/leap/util/geo.py32
-rw-r--r--src/leap/util/leap_argparse.py42
-rw-r--r--src/leap/util/misc.py37
-rw-r--r--src/leap/util/test_fileutil.py (renamed from src/leap/util/tests/test_fileutil.py)13
-rw-r--r--src/leap/util/test_leap_argparse.py (renamed from src/leap/util/tests/test_leap_argparse.py)12
-rw-r--r--src/leap/util/tests/__init__.py0
-rw-r--r--src/leap/util/tests/test_translations.py22
-rw-r--r--src/leap/util/translations.py82
-rw-r--r--src/leap/util/web.py40
-rw-r--r--tests/test_qt_environment.py6
-rw-r--r--tox.ini12
152 files changed, 3470 insertions, 15082 deletions
diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index bcf38d0f..00000000
--- a/.coveragerc
+++ /dev/null
@@ -1,23 +0,0 @@
-# .coveragerc to control coverage.py
-[run]
-branch = True
-
-[report]
-# Regexes for lines to exclude from consideration
-exclude_lines =
- # Have to re-enable the standard pragma
- pragma: no cover
-
- # Don't complain about missing debug-only code:
- def __repr__
- if self\.debug
-
- # Don't complain if tests don't hit defensive assertion code:
- raise AssertionError
- raise NotImplementedError
-
- # Don't complain if non-runnable code isn't run:
- if 0:
- if __name__ == .__main__.:
-
-ignore_errors = True
diff --git a/.gitattributes b/.gitattributes
index eb8672e0..36222847 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -16,4 +16,3 @@ man/ export-ignore
share/ export-ignore
src/leap.egg-info/ export-ignore
src/leap_client.egg-info export-ignore
-src/leap/_version.py export-subst
diff --git a/.gitignore b/.gitignore
index a9b7c1c8..26838e5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,7 @@
*.swp
*.swo
*.pyc
-*.log
.*
-!.coveragerc
-!.tx
bin/
build/
core
@@ -15,13 +12,7 @@ docs/covhtml
include/
lib/
local/
+man/
share/
src/leap.egg-info/
src/leap_client.egg-info
-src/leap/_branding.py
-src/leap/certs/*.pem
-src/*.egg-info
-pkg/osx/dist
-pkg/osx/build
-MANIFEST
-_trial_temp*
diff --git a/.tx/config b/.tx/config
deleted file mode 100644
index db998b21..00000000
--- a/.tx/config
+++ /dev/null
@@ -1,10 +0,0 @@
-[main]
-host = https://www.transifex.com
-
-[leap-client.leap-client]
-
-file_filter = data/translations/<lang>.ts
-source_file = data/ts/en_US.ts
-source_lang = en
-type = QT
-#minimum_perc = 90 # minimum percentage completed before pulling
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 00000000..e92537c0
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1 @@
+-- 0.1.0 initial release
diff --git a/MANIFEST.in b/MANIFEST.in
index d7a5201e..3ce64e45 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,3 @@
-include pkg/*
-include pkg/branding/*
+# ??? not needed from win
+include setup/linux/polkit/*
include docs/*
-include versioneer.py
diff --git a/Makefile b/Makefile
index 8d63232f..451ca201 100644
--- a/Makefile
+++ b/Makefile
@@ -1,37 +1,26 @@
-SHELL := /bin/zsh
# ################################
# Makefile for compiling resources
# files.
# TODO move to setup scripts
# and implement it in python
# http://die-offenbachs.homelinux.org:48888/hg/eric5/file/5072605ad4dd/compileUiFiles.py
-###### EDIT ######################
-
+###### EDIT ######################
#Directory with ui and resource files
RESOURCE_DIR = data/resources
#Directory for compiled resources
COMPILED_DIR = src/leap/gui
-
-#Directory for (finished) translations
-TRANSLAT_DIR = data/translations
-
-#Project file, used for translations
-PROJFILE = data/leap_client.pro
#UI files to compile
# UI_FILES = foo.ui
UI_FILES =
#Qt resource files to compile
#images.qrc
-RESOURCES = mainwindow.qrc locale.qrc
+RESOURCES = mainwindow.qrc
#pyuic4 and pyrcc4 binaries
PYUIC = pyuic4
PYRCC = pyrcc4
-PYLUP = pylupdate4
-LRELE = lrelease
-
#################################
# DO NOT EDIT FOLLOWING
@@ -48,10 +37,6 @@ all : resources ui
resources : $(COMPILED_RESOURCES)
ui : $(COMPILED_UI)
-
-translations:
- $(PYLUP) $(PROJFILE)
- $(LRELE) $(TRANSLAT_DIR)/*.ts
$(COMPILED_DIR)/ui_%.py : $(RESOURCE_DIR)/%.ui
$(PYUIC) $< -o $@
@@ -59,6 +44,12 @@ $(COMPILED_DIR)/ui_%.py : $(RESOURCE_DIR)/%.ui
$(COMPILED_DIR)/%_rc.py : $(RESOURCE_DIR)/%.qrc
$(PYRCC) $< -o $@
+deb:
+ #XXX finish this!
+ #should tag upstream/VERSION in upstream branch...
+ #@git tag -a upstream/$(DEBVER) -m "..."
+ @git-buildpackage --git-ignore-new --git-builder="debuild -us -uc -i'.*|bin|share|lib|local|include|\.git'" --git-upstream-branch=upstream --git-upstream-tree=branch --git-debian-branch=debian
+
manpages:
rst2man docs/man/leap-client.1.rst docs/man/leap-client.1
diff --git a/NEWS.rst b/NEWS.rst
deleted file mode 100644
index 45f1012f..00000000
--- a/NEWS.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-==================================
-User-facing changes in Leap Client
-==================================
-
-Release 0.2.0 (2013-1-XX)
---------------------------
-
-This release is a functionally working version in Debian Wheezy and Ubuntu 12.04.
-It is able to connect to a preconfigured leap provider and autoconfigures a EIP connection.
-
-Python Support
-''''''''''''''
-This release supports Python2.6 and Python2.7
-
-New Features
-''''''''''''
-- First run wizard: allows to register an user with the selected provider. It also downloads all
- the config files needed to connect to the eip service on this provider.
-- Network checks: we do some basic network testing and warn user in case we cannot find a
- suitable network interface, or if the virtual interface dissapears after a successful eip connection.
-- Debug mode and logfiles: the leap-client script allows to be invoked with the --debug flag.
- It also accepts a --logfile option that is useful for documenting bug reports.
-
-Dependencies
-''''''''''''
-See the ``README.rst`` for a step-to-step install guide.
-
-The following libraries are needed:
-
-- PyQt4
-- libgnutls
-- openvpn
-
-for building the package dependencies, you will need also:
-
-- python-setuptools
-- python-dev
-- libgnutls-dev
-
-Leap-Client depends on the following python packages:
-
-- pyopenssl
-- requests
-- psutil
-- netifaces
-- jsonschema
-- srp
-- pycrypto
-- keyring
-
-We are freezing the python-gnutls library dependency for this release due to a bug in ubuntu, see:
-https://bugs.launchpad.net/ubuntu/+source/python-gnutls/+bug/1027129
-
-
-Configuration files
-'''''''''''''''''''
-
-Config files are created under ``~/.config/leap``
-Currently user should be able to completely remove this folder and have it auto-generated in the first run.
-
-- Current eip service config is stored in ``eip.json``
-- Under ``.config/leap/providers``, there is a per-provider folder that contains:
- - ``provider.json``, with all options for connecting to this provider.
- - ``eip-service.json``, with eip-specific configuration options,
- - ``keys/ca``, for a copy of the ca certificates used in the tls connections to provider.
- - ``keys/client``, for a local copy of leap user certificates used in the eip connection.
-- ``leap.conf`` for general application configurations (gui windows geometry, ...).
diff --git a/README.txt b/README.txt
new file mode 100644
index 00000000..510bda58
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,95 @@
+========================================
+= LEAP =
+= The LEAP Encryption Access Project =
+= your internet encryption toolkit =
+========================================
+
+Installation
+=============
+
+Dependencies
+--------------
+
+* python <= 2.7
+* python setuptools
+* qt4 libraries
+* python-qt4
+* python-nose, python-mock, python-coverage (if you want to run tests)
+
+If you are on a debian-based system, you can run:
+
+apt-get install python-qt4 python-qt4-doc pyqt4-dev-tools python-setuptools python-nose
+
+Install
+---------------
+
+If not using virtualenv:
+sudo python setup.py install
+
+If using virtualenv:
+python setup.py install
+
+
+Running the App
+-----------------
+
+You need to set up a provider in your eip.cfg file:
+
+cd ~/.config/leap
+vim eip.cfg
+
+[provider]
+remote_ip = XXX.XXX.XXX.XXX
+
+and then run:
+
+leap --debug
+
+(or python app.py --debug if you run it from the src/leap folder).
+
+Development
+==============
+
+Running tests
+-------------
+
+./run_tests.sh
+
+if you want to run specific tests, pass the (sub)module to nose:
+
+nosetests leap.util
+
+or
+
+nosetests leap.util.test_leap_argparse
+
+
+Test-deps
+---------
+
+have a look at setup/test-requires
+
+Hack
+--------------
+
+(recommended)
+virtualenv . # ensure your .gitignore knows about it
+bin/activate
+
+# you should probably simlink sip.so and PyQt4 to your system-wide
+# install, there are some issues with it.
+
+python setup.py develop
+
+# ... TBD: finish develop howto.
+# ... and explain how is python setup develop useful.
+
+Compiling resource/ui files
+-----------------------------
+
+You should refresh resource/ui files every time you
+change an image or a resource/ui (.ui / .qc). From
+the root folder:
+
+make ui
+make resources
diff --git a/data/TODO b/data/TODO
new file mode 100644
index 00000000..580227ac
--- /dev/null
+++ b/data/TODO
@@ -0,0 +1 @@
+icons file and stuff should be moved here at some point!
diff --git a/data/images/leapfrog.jpg b/data/images/leapfrog.jpg
new file mode 100644
index 00000000..a1ddf4bb
--- /dev/null
+++ b/data/images/leapfrog.jpg
Binary files differ
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 00000000..045d9700
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,6 @@
+python-leap-client for Debian
+-----------------------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ -- unknown <kali@croatan> Sat, 21 Jul 2012 00:11:05 -0700
diff --git a/debian/README.source b/debian/README.source
new file mode 100644
index 00000000..da12f753
--- /dev/null
+++ b/debian/README.source
@@ -0,0 +1,9 @@
+leap-client for Debian
+-----------------------------
+
+<this file describes information about the source package, see Debian policy
+manual section 4.14. You WILL either need to modify or delete this file>
+
+
+
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..cb4de3d5
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,14 @@
+leap-client (0.2.0~rc+d53729f) unstable; urgency=low
+ [ Kali ]
+ * fixes to sphinxdoc install
+ * merged release/v0.2.0 branch
+ * updated dependency list
+ [ Micah Anderson ]
+ * removed unused comments from control file
+ * move build-depends to source package section of control file
+ * switch source/format to 3.0 (native)
+ * fix build-dependency on python-mock
+ [ Kali ]
+ * Initial release (Closes: #XXX)
+
+ -- kali <kali@leap.se> Sat, 21 Jul 2012 00:11:05 -0700
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..45a4fb75
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..c94189ca
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,47 @@
+Source: leap-client
+Section: misc
+Priority: extra
+Maintainer: Micah Anderson <micah@debian.org>
+Standards-Version: 3.9.3
+Homepage: http://leap.se
+#Vcs-Git: git://git.debian.org/python-applications-team/leap-client.git
+#Vcs-Browser: http://git.debian.org/?p=python-applications-team/leap-client.git;a=summary
+Build-Depends: debhelper (>= 8.0.0), python-support, python (>=2.6), python-sphinx (>= 1.0.7+dfsg)
+X-Python-Version: >= 2.6
+
+Package: leap-client
+Architecture: any
+Depends: ${misc:Depends}, ${python:Depends},
+ python-qt4,
+ python-openssl,
+ python-crypto,
+ python-keyring,
+ python-srp,
+ python-dateutil,
+ python-argparse,
+ python-psutil,
+ python-netifaces,
+ python-requests,
+ python-xdg,
+ python-jsonschema,
+ python-sh,
+ python-setuptools,
+ python-nose,
+ python-mock,
+ pep8,
+ openvpn
+#pyqt4-dev-tools, ???
+#python-sphinx, only build-depend, right?
+Suggests: resolvconf
+#, python-geoip ???
+Conflicts: autoresolv
+Enhances: openvpn
+Description: Provides the desktop client for the LEAP Encryption Access Project Platform.
+ LEAP (LEAP Encryption Access Project) develops
+ a plan to secure everyday communication, breaking down
+ into discrete services.
+ .
+ The client for the current phase gives support to the EIP Service.
+ EIP (the Encrypted Internet Proxy) provides circumvention, location
+ anonymization, and traffic encryption in a hassle-free,
+ automatically self-configuring fashion, and has an enhanced level of security.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..fff117e1
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,17 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: leap-client
+Upstream-Contact: info@leap.se
+Source: <http://github.com/leapcode/leap_client/>
+
+Files: *
+Copyright: 2012,2013 The LEAP Encryption Access Project
+License: GPL-3+
+ This package is released under the GNU GPL, version 3 or a later revision.
+ For further details see the COPYING file.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+# REVIEW-ME This needs a licensecheck -r run !!! -- kali
+# Do we need to add the GPL OpenSSL exception??
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 00000000..fc8df7fd
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+# XXX fix path...
+#NEWS.rst
+#README.rst
diff --git a/debian/init.d.ex b/debian/init.d.ex
new file mode 100644
index 00000000..3eec795a
--- /dev/null
+++ b/debian/init.d.ex
@@ -0,0 +1,154 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: python-leap-client
+# Required-Start: $network $local_fs
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description: <Enter a long description of the software>
+# <...>
+# <...>
+### END INIT INFO
+
+# Author: unknown <cal@croatan>
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC=python-leap-client # Introduce a short description here
+NAME=python-leap-client # Introduce the short server's name here
+DAEMON=/usr/sbin/python-leap-client # Introduce the server's location here
+DAEMON_ARGS="" # Arguments to run the daemon with
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/leap-client.doc-base.EX b/debian/leap-client.doc-base.EX
new file mode 100644
index 00000000..e70c2917
--- /dev/null
+++ b/debian/leap-client.doc-base.EX
@@ -0,0 +1,20 @@
+Document: python-leap-client
+Title: Debian python-leap-client Manual
+Author: <insert document author here>
+Abstract: This manual describes what python-leap-client is
+ and how it can be used to
+ manage online manuals on Debian systems.
+Section: unknown
+
+Format: debiandoc-sgml
+Files: /usr/share/doc/python-leap-client/python-leap-client.sgml.gz
+
+Format: postscript
+Files: /usr/share/doc/python-leap-client/python-leap-client.ps.gz
+
+Format: text
+Files: /usr/share/doc/python-leap-client/python-leap-client.text.gz
+
+Format: HTML
+Index: /usr/share/doc/python-leap-client/html/index.html
+Files: /usr/share/doc/python-leap-client/html/*.html
diff --git a/debian/leap-client.install b/debian/leap-client.install
new file mode 100644
index 00000000..c468a909
--- /dev/null
+++ b/debian/leap-client.install
@@ -0,0 +1 @@
+pkg/linux/polkit/net.openvpn.gui.leap.policy usr/share/polkit-1/actions/
diff --git a/debian/menu.ex b/debian/menu.ex
new file mode 100644
index 00000000..0ac5a68c
--- /dev/null
+++ b/debian/menu.ex
@@ -0,0 +1,2 @@
+?package(python-leap-client):needs="X11|text|vc|wm" section="Applications/see-menu-manual"\
+ title="python-leap-client" command="/usr/bin/python-leap-client"
diff --git a/debian/postinst.ex b/debian/postinst.ex
new file mode 100644
index 00000000..888928ca
--- /dev/null
+++ b/debian/postinst.ex
@@ -0,0 +1,39 @@
+#!/bin/sh
+# postinst script for python-leap-client
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/postrm.ex b/debian/postrm.ex
new file mode 100644
index 00000000..5048c8e2
--- /dev/null
+++ b/debian/postrm.ex
@@ -0,0 +1,37 @@
+#!/bin/sh
+# postrm script for python-leap-client
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <overwriter>
+# <overwriter-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/preinst.ex b/debian/preinst.ex
new file mode 100644
index 00000000..8aeafcfe
--- /dev/null
+++ b/debian/preinst.ex
@@ -0,0 +1,35 @@
+#!/bin/sh
+# preinst script for python-leap-client
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/prerm.ex b/debian/prerm.ex
new file mode 100644
index 00000000..19cc6ca1
--- /dev/null
+++ b/debian/prerm.ex
@@ -0,0 +1,38 @@
+#!/bin/sh
+# prerm script for python-leap-client
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <prerm> `remove'
+# * <old-prerm> `upgrade' <new-version>
+# * <new-prerm> `failed-upgrade' <old-version>
+# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+# * <deconfigured's-prerm> `deconfigure' `in-favour'
+# <package-being-installed> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ remove|upgrade|deconfigure)
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..198e923f
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,65 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+#
+# Uncomment this to turn on verbose mode.
+#DH_VERBOSE=1
+
+PYTHON2=$(shell pyversions -vr)
+
+%:
+ dh $@ --with python2,sphinxdoc --buildsystem=python_distutils
+
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+
+# run tests!
+#
+
+# have to fix running tests inside venv.
+# XXX breaking git-buildpackage process
+# if we choose to run inside venv
+test-python%:
+ ./run_tests.sh
+
+override_dh_auto_test: $(PYTHON2:%=test-python%) $(PYTHON3:%=test-python%)
+endif
+
+override_dh_preps:
+ # XXX trying to generate the manpage from rst,
+ # but not the right override... :(
+ rst2html docs/man/leap-client.1.rst docs/man/leap-client.1
+ dh_preps
+
+#dh_auto_build should be enough to build the python2 version
+
+build-python%:
+ python$* setup.py build
+
+#override_dh_auto_build: $(PYTHON3:%=build-python%)
+# dh_auto_build
+
+install-python%:
+ python$* setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb
+
+#override_dh_auto_install: $(PYTHON3:%=install-python%)
+# dh_auto_install
+
+override_dh_installchangelogs:
+ dh_installchangelogs -k CHANGES.txt
+
+# build and install sphinx docs
+#
+override_dh_installdocs:
+ python setup.py build_sphinx
+ dh_installdocs build/sphinx/html
+
+override_dh_auto_clean:
+ dh_auto_clean
+ rm -rf build
+ rm -rf docs/_build
+ rm -rf *.egg-info
+ #rm docs/man/leap.1
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 00000000..89ae9db8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/debian/source/include-binaries b/debian/source/include-binaries
new file mode 100644
index 00000000..9868b8e2
--- /dev/null
+++ b/debian/source/include-binaries
@@ -0,0 +1 @@
+dist/leap_client-0.1dev-py2.7.egg
diff --git a/debian/watch.ex b/debian/watch.ex
new file mode 100644
index 00000000..77f571a6
--- /dev/null
+++ b/debian/watch.ex
@@ -0,0 +1,10 @@
+# Example watch control file for uscan
+# Rename this file to "watch" and then you can run the "uscan" command
+# to check for upstream updates and more.
+# See uscan(1) for format
+
+# Compulsory line, this is a version 3 file
+version=3
+
+# XXX Leaving this here until we have leap.se downloads page -- kali
+https://github.com/leapcode/leap_client/tags.*/v(\d[\d\.]+)\.tar\.gz
diff --git a/ez_setup.py b/ez_setup.py
new file mode 100644
index 00000000..b74adc06
--- /dev/null
+++ b/ez_setup.py
@@ -0,0 +1,284 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from ez_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c11"
+DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+ 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+ 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+ 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+ 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+ 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+ 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+ 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+ 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+ 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+ 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+ 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090',
+ 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4',
+ 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7',
+ 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5',
+ 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de',
+ 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b',
+ 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2',
+ 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086',
+ 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+ 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+ 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+ 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+ 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+ 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+ 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+ 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+ 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+ 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+ 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+ 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+ 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+ 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+ 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+ 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+ 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+ 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
+ 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
+ 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
+ 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
+ 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
+ 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
+ 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
+}
+
+import sys, os
+try: from hashlib import md5
+except ImportError: from md5 import md5
+
+def _validate_md5(egg_name, data):
+ if egg_name in md5_data:
+ digest = md5(data).hexdigest()
+ if digest != md5_data[egg_name]:
+ print >>sys.stderr, (
+ "md5 validation of %s failed! (Possible download problem?)"
+ % egg_name
+ )
+ sys.exit(2)
+ return data
+
+def use_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ download_delay=15
+):
+ """Automatically find/download setuptools and make it available on sys.path
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end with
+ a '/'). `to_dir` is the directory where setuptools will be downloaded, if
+ it is not already available. If `download_delay` is specified, it should
+ be the number of seconds that will be paused before initiating a download,
+ should one be required. If an older version of setuptools is installed,
+ this routine will print a message to ``sys.stderr`` and raise SystemExit in
+ an attempt to abort the calling script.
+ """
+ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
+ def do_download():
+ egg = download_setuptools(version, download_base, to_dir, download_delay)
+ sys.path.insert(0, egg)
+ import setuptools; setuptools.bootstrap_install_from = egg
+ try:
+ import pkg_resources
+ except ImportError:
+ return do_download()
+ try:
+ pkg_resources.require("setuptools>="+version); return
+ except pkg_resources.VersionConflict, e:
+ if was_imported:
+ print >>sys.stderr, (
+ "The required version of setuptools (>=%s) is not available, and\n"
+ "can't be installed while this script is running. Please install\n"
+ " a more recent version first, using 'easy_install -U setuptools'."
+ "\n\n(Currently using %r)"
+ ) % (version, e.args[0])
+ sys.exit(2)
+ except pkg_resources.DistributionNotFound:
+ pass
+
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return do_download()
+
+def download_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ delay = 15
+):
+ """Download setuptools from a specified location and return its filename
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download attempt.
+ """
+ import urllib2, shutil
+ egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+ url = download_base + egg_name
+ saveto = os.path.join(to_dir, egg_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ from distutils import log
+ if delay:
+ log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help). I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+ %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+ version, download_base, delay, url
+ ); from time import sleep; sleep(delay)
+ log.warn("Downloading %s", url)
+ src = urllib2.urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = _validate_md5(egg_name, src.read())
+ dst = open(saveto,"wb"); dst.write(data)
+ finally:
+ if src: src.close()
+ if dst: dst.close()
+ return os.path.realpath(saveto)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ try:
+ import setuptools
+ except ImportError:
+ egg = None
+ try:
+ egg = download_setuptools(version, delay=0)
+ sys.path.insert(0,egg)
+ from setuptools.command.easy_install import main
+ return main(list(argv)+[egg]) # we're done here
+ finally:
+ if egg and os.path.exists(egg):
+ os.unlink(egg)
+ else:
+ if setuptools.__version__ == '0.0.1':
+ print >>sys.stderr, (
+ "You have an obsolete version of setuptools installed. Please\n"
+ "remove it from your system entirely before rerunning this script."
+ )
+ sys.exit(2)
+
+ req = "setuptools>="+version
+ import pkg_resources
+ try:
+ pkg_resources.require(req)
+ except pkg_resources.VersionConflict:
+ try:
+ from setuptools.command.easy_install import main
+ except ImportError:
+ from easy_install import main
+ main(list(argv)+[download_setuptools(delay=0)])
+ sys.exit(0) # try to force an exit
+ else:
+ if argv:
+ from setuptools.command.easy_install import main
+ main(argv)
+ else:
+ print "Setuptools version",version,"or greater has been installed."
+ print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+def update_md5(filenames):
+ """Update our built-in md5 registry"""
+
+ import re
+
+ for name in filenames:
+ base = os.path.basename(name)
+ f = open(name,'rb')
+ md5_data[base] = md5(f.read()).hexdigest()
+ f.close()
+
+ data = [" %r: %r,\n" % it for it in md5_data.items()]
+ data.sort()
+ repl = "".join(data)
+
+ import inspect
+ srcfile = inspect.getsourcefile(sys.modules[__name__])
+ f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+ match = re.search("\nmd5_data = {\n([^}]+)}", src)
+ if not match:
+ print >>sys.stderr, "Internal error!"
+ sys.exit(2)
+
+ src = src[:match.start(1)] + repl + src[match.end(1):]
+ f = open(srcfile,'w')
+ f.write(src)
+ f.close()
+
+
+if __name__=='__main__':
+ if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+ update_md5(sys.argv[2:])
+ else:
+ main(sys.argv[1:])
+
+
+
+
+
+
diff --git a/openvpn/README b/openvpn/README
deleted file mode 100644
index bf2205c2..00000000
--- a/openvpn/README
+++ /dev/null
@@ -1,6 +0,0 @@
-OpenVPN binary, build scripts
-Works using a GCC minGW32 cross-compiler on Debian/Ubuntu
-Produces a working MS Windows executable
-openvpn.exe: PE32 executable (DLL) (console) Intel 80386, for MS Windows
-goes smooth for the 99%, might still need some slapping the flags around now and then
- -jrml
diff --git a/openvpn/Sources b/openvpn/Sources
deleted file mode 100644
index e2fe7bb3..00000000
--- a/openvpn/Sources
+++ /dev/null
@@ -1,4 +0,0 @@
-lzo -2.06 .tar.gz
-opensc -0.12.2 .tar.gz
-openssl -1.0.1c .tar.gz
-polarssl -1.1.4 .tgz
diff --git a/openvpn/build.zsh b/openvpn/build.zsh
deleted file mode 100755
index b36717c1..00000000
--- a/openvpn/build.zsh
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/bin/zsh
-#
-# Copyright (C) 2012 Denis Roio <jaromil@dyne.org>
-#
-# This source code is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This source code is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# Please refer to the GNU Public License for more details.
-#
-# You should have received a copy of the GNU Public License along with
-# this source code; if not, write to:
-# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-
-REPO="http://files.dyne.org/leap/openvpn/sources"
-TOPSRC=`pwd`
-QUIET=0
-DEBUG=0
-
-
-autoload colors; colors
-# standard output message routines
-# it's always useful to wrap them, in case we change behaviour later
-notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi }
-error() { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[default] $1" >&2; fi }
-func() { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[default] $1" >&2; fi }
-act() {
- if [[ $QUIET == 0 ]]; then
- if [ "$1" = "-n" ]; then
- print -n "$fg_bold[white] . $fg_no_bold[default] $2" >&2;
- else
- print "$fg_bold[white] . $fg_no_bold[default] $1" >&2;
- fi
- fi
-}
-
-{ test "$1" = "clean" } && {
- notice "Cleaning up all build in ${TOPSRC}"
- for src in `cat Sources | awk '
-/^#/ {next}
-/^./ { print $1 }'`; do
- { test "$src" != "" } && { rm -rf "${src}" }
- done
- act "Done."
- return 0
-}
-
-os="`uname -s`"
-target="$1"
-notice "OpenVPN build on $os for $target in ${TOPSRC}"
-
-prepare_sources() {
- notice "Preparing sources"
- # look for a file names "Sources", download and decompress entries
- # format of file: name version compression (complete filename when merged)
- { test -r Sources } || {
- error "Sources not found, nothing to build here"
- return 1
- }
- for src in `cat Sources | awk '
-/^#/ {next}
-/^./ { print $1 ";" $2 ";" $3 }'`; do
- name="${src[(ws:;:)1]}"
- ver="${src[(ws:;:)2]}"
- arch="${src[(ws:;:)3]}"
- file="${name}${ver}${arch}"
- func "preparing source for ${name}${ver}"
-
- { test "$1" != "" } && {
- test "$1" != "$name" } && {
- continue }
-
- # download the file
- { test -r ${file} } || {
- act "downloading ${file}"
- curl ${REPO}/${file} -o ${file}
- }
- # decompress the file
- { test -r ${name} } || {
- act "decompressing ${name}"
- case $arch in
- ## BARE SOURCE
- .tar.gz) tar xfz ${file}; mv ${name}${ver} ${name} ;;
- .tar.bz2) tar xfj ${file}; mv ${name}${ver} ${name} ;;
- .tgz) tar xfz ${file}; mv ${name}${ver} ${name} ;;
- *) error "compression not supported: $arch"
- esac
- }
- act "${name} source ready"
- done
-}
-
-act "Downloading sources"
-
-# git clone latest openvpn
-{ test -r openvpn } || { git clone https://github.com/OpenVPN/openvpn.git }
-
-case "$os" in
- Darwin)
- prepare_sources lzo
- prepare_sources polarssl
- ;;
- Linux) # Cross-compile for Win32
- prepare_sources lzo
- prepare_sources opensc
- prepare_sources openssl
- # tap windows
- { test -r tap-windows } || { git clone https://github.com/OpenVPN/tap-windows.git }
- ;;
-esac
-
-notice "Sources ready, now compiling..."
-LOG="`pwd`/build.log"; touch ${LOG}
-act "logs saved in build.log"
-
-case "$target" in
- osx)
- { test -r polarssl/library/libpolarssl.a } || {
- act "building PolarSSL..."
- pushd polarssl
- CC=clang cmake . >> ${LOG}
- make -C library clean
- cat CMakeCache.txt | awk '
-/^CMAKE_C_COMPILER/ { print "CMAKE_C_COMPILER:FILEPATH=/usr/bin/clang"; next }
-/^CMAKE_BUILD_TYPE/ { print $1 "Release"; next }
-/^CMAKE_C_FLAGS:STRING/ { print "CMAKE_C_FLAGS:STRING=-arch x86_64 -arch i386"; next }
-{ print $0 }
-' > CMakeCache.leap
- cp CMakeCache.leap CMakeCache.txt
- make -C library >> ${LOG}
- popd
- act "done."
- }
-
- act "building OpenVPN"
- pushd openvpn
- CC=clang CFLAGS="-arch x86_64 -arch i386" \
- LZO_LIBS="/opt/local/lib/liblzo2.a" LZO_CFLAGS="-I/opt/local/include" \
- POLARSSL_CFLAGS="-I${TOPSRC}/polarssl/include" \
- POLARSSL_LIBS="${TOPSRC}/polarssl/library/libpolarssl.a" \
- ./configure --with-crypto-library=polarssl >> ${LOG}
- make src/openvpn/openvpn
- popd
- act "done."
- ;;
-
- win32)
- { test -r lzo/src/liblzo2.la } || { pushd lzo
- act "building LZO lib"
- ./configure --host=i586-mingw32msvc >> ${LOG}
- make >> ${LOG}; popd }
- # openssl
- { test -r openssl/libssl.a } || {
- act "building OpenSSL lib"
- pushd openssl
- ./Configure --cross-compile-prefix=i586-mingw32msvc- mingw >> ${LOG}
- make ${LOG}; popd }
-
- pushd openvpn
- act "building latest OpenVPN"
- { test -r configure } || {
- sed -i -e 's/-municode//' src/openvpn/Makefile.am
- autoreconf -i >> ${LOG}
- }
- CFLAGS="-I/usr/i586-mingw32msvc/include/ddk -D_WIN32_WINNT=0x0501" \
- LZO_LIBS="${TOPSRC}/lzo/src/liblzo2.la" \
- LZO_CFLAGS="-I${TOPSRC}/lzo/include" \
- TAP_CFLAGS="-I${TOPSRC}/tap-windows/src" \
- OPENSSL_SSL_CFLAGS="-I${TOPSRC}/openssl/include" \
- OPENSSL_CRYPTO_CFLAGS="-I${TOPSRC}/openssl/crypto" \
- OPENSSL_SSL_LIBS="${TOPSRC}/openssl/libssl.a" \
- OPENSSL_CRYPTO_LIBS="${TOPSRC}/openssl/libcrypto.a" \
- ./configure --host=i586-mingw32msvc >> ${LOG}
- make >> ${LOG}
- popd
-
- act "If OpenVPN build reports a final error on linkage, it might be due to a libtool bug"
- act "(something like undefined reference to _WinMain@16)"
- act "You need to go inside openvpn/src/openvpn and issue the last compile line manually"
- act "adding an flat '-shared' at the end of it, then do 'cp .libs/openvpn.exe .'"
- act "Happy hacking."
- ;;
- *)
- error "Unknown target: $target"
- ;;
-esac
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 89b0ad3b..6a5f9311 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -14,4 +14,4 @@ python-dateutil
sh
pyxdg
-pygeoip # optional
+#pygeoip # optional
diff --git a/run_tests.sh b/run_tests.sh
index 7cbed018..ebea30b2 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -15,7 +15,6 @@ function usage {
echo " -P, --no-pep8 Don't run pep8"
echo " -c, --coverage Generate coverage report"
echo " -h, --help Print this usage message"
- echo " -A, --all Run all tests, without excluding any"
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
echo ""
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
@@ -34,14 +33,13 @@ function process_option {
-p|--pep8) just_pep8=1;;
-P|--no-pep8) no_pep8=1;;
-c|--coverage) coverage=1;;
- -A|--all) alltests=1;;
-*) noseopts="$noseopts $1";;
*) noseargs="$noseargs $1"
esac
}
venv=.venv
-with_venv=pkg/tools/with_venv.sh
+with_venv=setup/tools/with_venv.sh
always_venv=0
never_venv=0
force=0
@@ -53,7 +51,6 @@ wrapper=""
just_pep8=0
no_pep8=0
coverage=0
-alltests=0
for arg in "$@"; do
process_option $arg
@@ -68,11 +65,6 @@ if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
-# If alltests flag is not set, let's exclude some dirs that are troublesome.
-if [ $alltests -eq 0 ]; then
- noseopts="$noseopts --exclude-dir=src/leap/soledad"
-fi
-
function run_tests {
# Just run the test suites in current environment
${wrapper} $NOSETESTS
@@ -85,7 +77,7 @@ function run_pep8 {
echo "Running pep8 ..."
srcfiles="src/leap tests"
# Just run PEP8 in current environment
- pep8_opts="--ignore=E202,W602 --exclude=*_rc.py,_version.py --repeat"
+ pep8_opts="--ignore=E202,W602 --exclude=*_rc.py --repeat"
${wrapper} pep8 ${pep8_opts} ${srcfiles}
}
@@ -107,14 +99,14 @@ then
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
- python pkg/install_venv.py $installvenvopts
+ python setup/install_venv.py $installvenvopts
wrapper="${with_venv}"
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
- python pkg/install_venv.py $installvenvopts
+ python setup/install_venv.py $installvenvopts
wrapper=${with_venv}
fi
fi
@@ -140,10 +132,9 @@ if [ -z "$noseargs" ]; then
fi
function run_coverage {
- cov_opts="--omit=`pwd`/src/leap/base/tests/*,`pwd`/src/leap/eip/tests/*,`pwd`/src/leap/gui/tests/*"
- cov_opts="$cov_opts,`pwd`/src/leap/util/tests/* "
- cov_opts="$cov_opts --include=`pwd`/src/leap/*" #,`pwd`/src/leap/eip/*"
- ${wrapper} coverage html -d docs/covhtml -i $cov_opts
+ # XXX not working? getting 3rd party modules
+ coverage_opts="--include `pwd`/src/leap/*,`pwd`/src/leap/eip/*"
+ ${wrapper} coverage html -d docs/covhtml -i $coverage_opts
echo "now point your browser at docs/covhtml/index.html"
}
diff --git a/setup.cfg b/setup.cfg
index 4b049f97..01bb9544 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,3 @@
[egg_info]
-#tag_build = dev
+tag_build = dev
+tag_svn_revision = true
diff --git a/setup.py b/setup.py
index 67d8ea5c..eda727fd 100755
--- a/setup.py
+++ b/setup.py
@@ -208,7 +208,7 @@ setup(
zip_safe=False,
# not being used since setuptools does not like it.
- # looks like debhelper is honoring it...
+ # but looks that debhelper honors it...
data_files=[
("share/man/man1",
["docs/man/leap-client.1"]),
diff --git a/setup/install_venv.py b/setup/install_venv.py
new file mode 100644
index 00000000..3f3f0575
--- /dev/null
+++ b/setup/install_venv.py
@@ -0,0 +1,242 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2010 OpenStack, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Installation script for Nova's development virtualenv
+"""
+
+import optparse
+import os
+import subprocess
+import sys
+import platform
+
+
+ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+VENV = os.path.join(ROOT, '.venv')
+PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
+TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires')
+PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+
+
+def die(message, *args):
+ print >> sys.stderr, message % args
+ sys.exit(1)
+
+
+def check_python_version():
+ if sys.version_info < (2, 6):
+ die("Need Python Version >= 2.6")
+
+
+def run_command_with_code(cmd, redirect_output=True, check_exit_code=True):
+ """
+ Runs a command in an out-of-process shell, returning the
+ output of that command. Working directory is ROOT.
+ """
+ if redirect_output:
+ stdout = subprocess.PIPE
+ else:
+ stdout = None
+
+ print 'executing command: %s', cmd
+ proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
+ output = proc.communicate()[0]
+ if check_exit_code and proc.returncode != 0:
+ die('Command "%s" failed.\n%s', ' '.join(cmd), output)
+ return (output, proc.returncode)
+
+
+def run_command(cmd, redirect_output=True, check_exit_code=True):
+ return run_command_with_code(cmd, redirect_output, check_exit_code)[0]
+
+
+class Distro(object):
+
+ def check_cmd(self, cmd):
+ return bool(run_command(['which', cmd], check_exit_code=False).strip())
+
+ def install_virtualenv(self):
+ if self.check_cmd('virtualenv'):
+ return
+
+ if self.check_cmd('easy_install'):
+ print 'Installing virtualenv via easy_install...',
+ if run_command(['easy_install', 'virtualenv']):
+ print 'Succeeded'
+ return
+ else:
+ print 'Failed'
+
+ die('ERROR: virtualenv not found.\n\nDevelopment'
+ ' requires virtualenv, please install it using your'
+ ' favorite package management tool')
+
+ def post_process(self):
+ """Any distribution-specific post-processing gets done here.
+
+ In particular, this is useful for applying patches to code inside
+ the venv."""
+ pass
+
+
+class Debian(Distro):
+ """This covers all Debian-based distributions."""
+
+ def check_pkg(self, pkg):
+ return run_command_with_code(['dpkg', '-l', pkg],
+ check_exit_code=False)[1] == 0
+
+ def apt_install(self, pkg, **kwargs):
+ run_command(['sudo', 'apt-get', 'install', '-y', pkg], **kwargs)
+
+ def apply_patch(self, originalfile, patchfile):
+ run_command(['patch', originalfile, patchfile])
+
+ def install_virtualenv(self):
+ if self.check_cmd('virtualenv'):
+ return
+
+ if not self.check_pkg('python-virtualenv'):
+ self.apt_install('python-virtualenv', check_exit_code=False)
+
+ super(Debian, self).install_virtualenv()
+
+
+class Fedora(Distro):
+ """This covers all Fedora-based distributions.
+
+ Includes: Fedora, RHEL, CentOS, Scientific Linux"""
+
+ def check_pkg(self, pkg):
+ return run_command_with_code(['rpm', '-q', pkg],
+ check_exit_code=False)[1] == 0
+
+ def yum_install(self, pkg, **kwargs):
+ run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
+
+ def apply_patch(self, originalfile, patchfile):
+ run_command(['patch', originalfile, patchfile])
+
+ def install_virtualenv(self):
+ if self.check_cmd('virtualenv'):
+ return
+
+ if not self.check_pkg('python-virtualenv'):
+ self.yum_install('python-virtualenv', check_exit_code=False)
+
+ super(Fedora, self).install_virtualenv()
+
+
+def get_distro():
+ if os.path.exists('/etc/fedora-release') or \
+ os.path.exists('/etc/redhat-release'):
+ return Fedora()
+ elif os.path.exists('/etc/debian_version'):
+ return Debian()
+ else:
+ return Distro()
+
+
+def check_dependencies():
+ get_distro().install_virtualenv()
+
+
+def create_virtualenv(venv=VENV, no_site_packages=True):
+ """Creates the virtual environment and installs PIP only into the
+ virtual environment
+ """
+ print 'Creating venv...',
+ if no_site_packages:
+ run_command(['virtualenv', '-q', '--no-site-packages', VENV])
+ else:
+ run_command(['virtualenv', '-q', VENV])
+ print 'done.'
+ print 'Installing pip in virtualenv...',
+ if not run_command(['setup/tools/with_venv.sh', 'easy_install',
+ 'pip>1.0']).strip():
+ die("Failed to install pip.")
+ print 'done.'
+
+
+def pip_install(*args):
+ run_command(['setup/tools/with_venv.sh',
+ 'pip', 'install', '--upgrade'] + list(args),
+ redirect_output=False)
+
+
+def install_dependencies(venv=VENV):
+ print 'Installing dependencies with pip (this can take a while)...'
+
+ # First things first, make sure our venv has the latest pip and distribute.
+ pip_install('pip')
+ pip_install('distribute')
+
+ pip_install('-r', PIP_REQUIRES)
+ pip_install('-r', TEST_REQUIRES)
+
+ # "
+ pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages",
+ "leap-client.pth")
+ f = open(pthfile, 'w')
+ f.write("%s\n" % ROOT)
+
+
+def post_process():
+ get_distro().post_process()
+
+
+def print_help():
+ help = """
+ To activate the leap virtualenv for the extent of your current
+ shell session you can run:
+
+ $ source .venv/bin/activate
+
+ Or, if you prefer, you can run commands in the virtualenv on a case by case
+ basis by running:
+
+ $ setup/tools/with_venv.sh <your command>
+
+ Also, make test will automatically use the virtualenv.
+ """
+ print help
+
+
+def parse_args():
+ """Parse command-line arguments"""
+ parser = optparse.OptionParser()
+ parser.add_option("-n", "--no-site-packages", dest="no_site_packages",
+ default=False, action="store_true",
+ help="Do not inherit packages from global Python install")
+ return parser.parse_args()
+
+
+def main(argv):
+ (options, args) = parse_args()
+ check_python_version()
+ check_dependencies()
+ create_virtualenv(no_site_packages=options.no_site_packages)
+ install_dependencies()
+ post_process()
+ print_help()
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/setup/linux/leap.desktop b/setup/linux/leap.desktop
new file mode 100644
index 00000000..7a6d39d9
--- /dev/null
+++ b/setup/linux/leap.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Version=0.1.0
+Encoding=UTF-8
+Name=EIP
+Comment=Anonymity and privacy
+Comment[en]=Anonymity and privacy
+Comment[es]=Anonimato y privacidad
+Comment[sv]=Anonymitet och avlyssningsskydd
+Exec=leap
+Terminal=false
+Type=Application
+Icon=leap.png
+Categories=Network;
diff --git a/setup/linux/polkit/net.openvpn.gui.leap.policy b/setup/linux/polkit/net.openvpn.gui.leap.policy
new file mode 100644
index 00000000..70a22b65
--- /dev/null
+++ b/setup/linux/polkit/net.openvpn.gui.leap.policy
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+<policyconfig>
+
+ <vendor>LEAP Project</vendor>
+ <vendor_url>http://leap.se/</vendor_url>
+
+ <action id="net.openvpn,gui.leap.run-openvpn">
+ <description>Runs the openvpn binary</description>
+ <description xml:lang="es">Ejecuta el binario openvpn</description>
+ <message>OpenVPN needs that you authenticate to start</message>
+ <message xml:lang="es">OpenVPN necesita autorizacion para comenzar</message>
+ <icon_name>package-x-generic</icon_name>
+ <defaults>
+ <allow_any>auth_self_keep</allow_any>
+ <allow_inactive>auth_self_keep</allow_inactive>
+ <allow_active>auth_self_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/openvpn</annotate>
+ </action>
+</policyconfig>
diff --git a/setup/requirements.pip b/setup/requirements.pip
new file mode 100644
index 00000000..1352d5e6
--- /dev/null
+++ b/setup/requirements.pip
@@ -0,0 +1 @@
+argparse
diff --git a/setup/scripts/leap b/setup/scripts/leap
new file mode 100755
index 00000000..6e62b597
--- /dev/null
+++ b/setup/scripts/leap
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+from leap.app import main
+
+if __name__ == "__main__":
+ main()
diff --git a/setup/test-requires b/setup/test-requires
new file mode 100644
index 00000000..26db61c8
--- /dev/null
+++ b/setup/test-requires
@@ -0,0 +1,5 @@
+coverage
+mock
+nose
+pep8==1.1
+sphinx>=1.1.2
diff --git a/setup/tools/with_venv.sh b/setup/tools/with_venv.sh
new file mode 100755
index 00000000..0e58f1ab
--- /dev/null
+++ b/setup/tools/with_venv.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+TOOLS=`dirname $0`
+VENV=$TOOLS/../../.venv
+source $VENV/bin/activate && $@
diff --git a/src/leap/__init__.py b/src/leap/__init__.py
index 0e880867..a7ae10e3 100644
--- a/src/leap/__init__.py
+++ b/src/leap/__init__.py
@@ -1,35 +1,5 @@
-"""
-LEAP Encryption Access Project
-website: U{https://leap.se/}
-"""
-
from leap import eip
from leap import baseapp
from leap import util
-#from leap import soledad
__all__ = [eip, baseapp, util]
-__version__ = "unknown"
-try:
- from ._version import get_versions
- __version__ = get_versions()['version']
- del get_versions
-except ImportError:
- #running on a tree that has not run
- #the setup.py setver
- pass
-
-__appname__ = "unknown"
-try:
- from leap._appname import __appname__
-except ImportError:
- #running on a tree that has not run
- #the setup.py setver
- pass
-
-__full_version__ = __appname__ + '/' + str(__version__)
-
-try:
- from leap._branding import BRANDING as __branding
-except ImportError:
- __branding = {}
diff --git a/src/leap/app.py b/src/leap/app.py
index eb38751c..db48701b 100644
--- a/src/leap/app.py
+++ b/src/leap/app.py
@@ -1,25 +1,13 @@
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
-from functools import partial
import logging
-import signal
-
# This is only needed for Python v2 but is harmless for Python v3.
import sip
sip.setapi('QVariant', 2)
-sip.setapi('QString', 2)
from PyQt4.QtGui import (QApplication, QSystemTrayIcon, QMessageBox)
-from PyQt4 import QtCore
-from leap import __version__ as VERSION
from leap.baseapp.mainwindow import LeapWindow
-from leap.gui import locale_rc
-
-def sigint_handler(*args, **kwargs):
- logger = kwargs.get('logger', None)
- logger.debug('SIGINT catched. shutting down...')
- mainwindow = args[0]
- mainwindow.shutdownSignal.emit()
+logging.basicConfig()
+logger = logging.getLogger(name=__name__)
def main():
@@ -32,88 +20,23 @@ def main():
parser, opts = leap_argparse.init_leapc_args()
debug = getattr(opts, 'debug', False)
- # XXX get severity from command line args
+ #XXX get debug level and set logger accordingly
if debug:
- level = logging.DEBUG
- else:
- level = logging.WARNING
-
- logger = logging.getLogger(name='leap')
- logger.setLevel(level)
- console = logging.StreamHandler()
- console.setLevel(level)
- formatter = logging.Formatter(
- '%(asctime)s '
- '- %(name)s - %(levelname)s - %(message)s')
- console.setFormatter(formatter)
- logger.addHandler(console)
+ logger.setLevel('DEBUG')
+ logger.debug('args: %s' % opts)
- logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
- logger.info('LEAP client version %s', VERSION)
- logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
- logfile = getattr(opts, 'log_file', False)
- if logfile:
- logger.debug('setting logfile to %s ', logfile)
- fileh = logging.FileHandler(logfile)
- fileh.setLevel(logging.DEBUG)
- fileh.setFormatter(formatter)
- logger.addHandler(fileh)
-
- logger.info('Starting app')
app = QApplication(sys.argv)
- # To test:
- # $ LANG=es ./app.py
- locale = QtCore.QLocale.system().name()
- qtTranslator = QtCore.QTranslator()
- if qtTranslator.load("qt_%s" % locale, ":/translations"):
- app.installTranslator(qtTranslator)
- appTranslator = QtCore.QTranslator()
- if appTranslator.load("leap_client_%s" % locale, ":/translations"):
- app.installTranslator(appTranslator)
-
- # needed for initializing qsettings
- # it will write .config/leap/leap.conf
- # top level app settings
- # in a platform independent way
- app.setOrganizationName("leap")
- app.setApplicationName("leap")
- app.setOrganizationDomain("leap.se")
-
- # XXX we could check here
- # if leap-client is already running, and abort
- # gracefully in that case.
-
if not QSystemTrayIcon.isSystemTrayAvailable():
QMessageBox.critical(None, "Systray",
- "I couldn't detect"
- "any system tray on this system.")
+ "I couldn't detect any \
+system tray on this system.")
sys.exit(1)
if not debug:
QApplication.setQuitOnLastWindowClosed(False)
window = LeapWindow(opts)
-
- # this dummy timer ensures that
- # control is given to the outside loop, so we
- # can hook our sigint handler.
- timer = QtCore.QTimer()
- timer.start(500)
- timer.timeout.connect(lambda: None)
-
- sigint_window = partial(sigint_handler, window, logger=logger)
- signal.signal(signal.SIGINT, sigint_window)
-
- if debug:
- # we only show the main window
- # if debug mode active.
- # if not, it will be set visible
- # from the systray menu.
- window.show()
- if sys.platform == "darwin":
- window.raise_()
-
- # run main loop
+ window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
diff --git a/src/leap/base/__init__.py b/src/leap/base/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/base/__init__.py
+++ /dev/null
diff --git a/src/leap/base/auth.py b/src/leap/base/auth.py
deleted file mode 100644
index c2d3f424..00000000
--- a/src/leap/base/auth.py
+++ /dev/null
@@ -1,355 +0,0 @@
-import binascii
-import json
-import logging
-#import urlparse
-
-import requests
-import srp
-
-from PyQt4 import QtCore
-
-from leap.base import constants as baseconstants
-from leap.crypto import leapkeyring
-from leap.util.misc import null_check
-from leap.util.web import get_https_domain_and_port
-
-logger = logging.getLogger(__name__)
-
-SIGNUP_TIMEOUT = getattr(baseconstants, 'SIGNUP_TIMEOUT', 5)
-
-"""
-Registration and authentication classes for the
-SRP auth mechanism used in the leap platform.
-
-We're using the srp library which uses a c-based implementation
-of the protocol if the c extension is available, and a python-based
-one if not.
-"""
-
-
-class SRPAuthenticationError(Exception):
- """
- exception raised
- for authentication errors
- """
-
-
-safe_unhexlify = lambda x: binascii.unhexlify(x) \
- if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
-
-
-class LeapSRPRegister(object):
-
- def __init__(self,
- schema="https",
- provider=None,
- verify=True,
- register_path="1/users",
- method="POST",
- fetcher=requests,
- srp=srp,
- hashfun=srp.SHA256,
- ng_constant=srp.NG_1024):
-
- null_check(provider, "provider")
-
- self.schema = schema
-
- domain, port = get_https_domain_and_port(provider)
- self.provider = domain
- self.port = port
-
- self.verify = verify
- self.register_path = register_path
- self.method = method
- self.fetcher = fetcher
- self.srp = srp
- self.HASHFUN = hashfun
- self.NG = ng_constant
-
- self.init_session()
-
- def init_session(self):
- self.session = self.fetcher.session()
-
- def get_registration_uri(self):
- # XXX assert is https!
- # use urlparse
- if self.port:
- uri = "%s://%s:%s/%s" % (
- self.schema,
- self.provider,
- self.port,
- self.register_path)
- else:
- uri = "%s://%s/%s" % (
- self.schema,
- self.provider,
- self.register_path)
-
- return uri
-
- def register_user(self, username, password, keep=False):
- """
- @rtype: tuple
- @rparam: (ok, request)
- """
- salt, vkey = self.srp.create_salted_verification_key(
- username,
- password,
- self.HASHFUN,
- self.NG)
-
- user_data = {
- 'user[login]': username,
- 'user[password_verifier]': binascii.hexlify(vkey),
- 'user[password_salt]': binascii.hexlify(salt)}
-
- uri = self.get_registration_uri()
- logger.debug('post to uri: %s' % uri)
-
- # XXX get self.method
- req = self.session.post(
- uri, data=user_data,
- timeout=SIGNUP_TIMEOUT,
- verify=self.verify)
- # we catch it in the form
- #req.raise_for_status()
- return (req.ok, req)
-
-
-class SRPAuth(requests.auth.AuthBase):
-
- def __init__(self, username, password, server=None, verify=None):
- # sanity check
- null_check(server, 'server')
- self.username = username
- self.password = password
- self.server = server
- self.verify = verify
-
- logger.debug('SRPAuth. verify=%s' % verify)
- logger.debug('server: %s. username=%s' % (server, username))
-
- self.init_data = None
- self.session = requests.session()
-
- self.init_srp()
-
- def init_srp(self):
- usr = srp.User(
- self.username,
- self.password,
- srp.SHA256,
- srp.NG_1024)
- uname, A = usr.start_authentication()
-
- self.srp_usr = usr
- self.A = A
-
- def get_auth_data(self):
- return {
- 'login': self.username,
- 'A': binascii.hexlify(self.A)
- }
-
- def get_init_data(self):
- try:
- init_session = self.session.post(
- self.server + '/1/sessions/',
- data=self.get_auth_data(),
- verify=self.verify)
- except requests.exceptions.ConnectionError:
- raise SRPAuthenticationError(
- "No connection made (salt).")
- except:
- raise SRPAuthenticationError(
- "Unknown error (salt).")
- if init_session.status_code not in (200, ):
- raise SRPAuthenticationError(
- "No valid response (salt).")
-
- self.init_data = init_session.json
- return self.init_data
-
- def get_server_proof_data(self):
- try:
- auth_result = self.session.put(
- #self.server + '/1/sessions.json/' + self.username,
- self.server + '/1/sessions/' + self.username,
- data={'client_auth': binascii.hexlify(self.M)},
- verify=self.verify)
- except requests.exceptions.ConnectionError:
- raise SRPAuthenticationError(
- "No connection made (HAMK).")
-
- if auth_result.status_code not in (200, ):
- raise SRPAuthenticationError(
- "No valid response (HAMK).")
-
- self.auth_data = auth_result.json
- return self.auth_data
-
- def authenticate(self):
- logger.debug('start authentication...')
-
- init_data = self.get_init_data()
- salt = init_data.get('salt', None)
- B = init_data.get('B', None)
-
- # XXX refactor this function
- # move checks and un-hex
- # to routines
-
- if not salt or not B:
- raise SRPAuthenticationError(
- "Server did not send initial data.")
-
- try:
- unhex_salt = safe_unhexlify(salt)
- except TypeError:
- raise SRPAuthenticationError(
- "Bad data from server (salt)")
- try:
- unhex_B = safe_unhexlify(B)
- except TypeError:
- raise SRPAuthenticationError(
- "Bad data from server (B)")
-
- self.M = self.srp_usr.process_challenge(
- unhex_salt,
- unhex_B
- )
-
- proof_data = self.get_server_proof_data()
-
- HAMK = proof_data.get("M2", None)
- if not HAMK:
- errors = proof_data.get('errors', None)
- if errors:
- logger.error(errors)
- raise SRPAuthenticationError("Server did not send HAMK.")
-
- try:
- unhex_HAMK = safe_unhexlify(HAMK)
- except TypeError:
- raise SRPAuthenticationError(
- "Bad data from server (HAMK)")
-
- self.srp_usr.verify_session(
- unhex_HAMK)
-
- try:
- assert self.srp_usr.authenticated()
- logger.debug('user is authenticated!')
- except (AssertionError):
- raise SRPAuthenticationError(
- "Auth verification failed.")
-
- def __call__(self, req):
- self.authenticate()
- req.cookies = self.session.cookies
- return req
-
-
-def srpauth_protected(user=None, passwd=None, server=None, verify=True):
- """
- decorator factory that accepts
- user and password keyword arguments
- and add those to the decorated request
- """
- def srpauth(fn):
- def wrapper(*args, **kwargs):
- if user and passwd:
- auth = SRPAuth(user, passwd, server, verify)
- kwargs['auth'] = auth
- kwargs['verify'] = verify
- if not args:
- logger.warning('attempting to get from empty uri!')
- return fn(*args, **kwargs)
- return wrapper
- return srpauth
-
-
-def get_leap_credentials():
- settings = QtCore.QSettings()
- full_username = settings.value('username')
- username, domain = full_username.split('@')
- seed = settings.value('%s_seed' % domain, None)
- password = leapkeyring.leap_get_password(full_username, seed=seed)
- return (username, password)
-
-
-# XXX TODO
-# Pass verify as single argument,
-# in srpauth_protected style
-
-def magick_srpauth(fn):
- """
- decorator that gets user and password
- from the config file and adds those to
- the decorated request
- """
- logger.debug('magick srp auth decorator called')
-
- def wrapper(*args, **kwargs):
- #uri = args[0]
- # XXX Ugh!
- # Problem with this approach.
- # This won't work when we're using
- # api.foo.bar
- # Unless we keep a table with the
- # equivalencies...
- user, passwd = get_leap_credentials()
-
- # XXX pass verify and server too
- # (pop)
- auth = SRPAuth(user, passwd)
- kwargs['auth'] = auth
- return fn(*args, **kwargs)
- return wrapper
-
-
-if __name__ == "__main__":
- """
- To test against test_provider (twisted version)
- Register an user: (will be valid during the session)
- >>> python auth.py add test password
-
- Test login with that user:
- >>> python auth.py login test password
- """
-
- import sys
-
- if len(sys.argv) not in (4, 5):
- print 'Usage: auth <add|login> <user> <pass> [server]'
- sys.exit(0)
-
- action = sys.argv[1]
- user = sys.argv[2]
- passwd = sys.argv[3]
-
- if len(sys.argv) == 5:
- SERVER = sys.argv[4]
- else:
- SERVER = "https://localhost:8443"
-
- if action == "login":
-
- @srpauth_protected(
- user=user, passwd=passwd, server=SERVER, verify=False)
- def test_srp_protected_get(*args, **kwargs):
- req = requests.get(*args, **kwargs)
- req.raise_for_status
- return req
-
- #req = test_srp_protected_get('https://localhost:8443/1/cert')
- req = test_srp_protected_get('%s/1/cert' % SERVER)
- #print 'cert :', req.content[:200] + "..."
- print req.content
- sys.exit(0)
-
- if action == "add":
- auth = LeapSRPRegister(provider=SERVER, verify=False)
- auth.register_user(user, passwd)
diff --git a/src/leap/base/authentication.py b/src/leap/base/authentication.py
deleted file mode 100644
index 09ff1d07..00000000
--- a/src/leap/base/authentication.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-Authentication Base Class
-"""
-
-
-class Authentication(object):
- """
- I have no idea how Authentication (certs,?)
- will be done, but stub it here.
- """
- pass
diff --git a/src/leap/base/checks.py b/src/leap/base/checks.py
deleted file mode 100644
index 0bf44f59..00000000
--- a/src/leap/base/checks.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# -*- coding: utf-8 -*-
-import logging
-import platform
-import re
-import socket
-
-import netifaces
-import sh
-
-from leap.base import constants
-from leap.base import exceptions
-
-logger = logging.getLogger(name=__name__)
-_platform = platform.system()
-
-#EVENTS OF NOTE
-EVENT_CONNECT_REFUSED = "[ECONNREFUSED]: Connection refused (code=111)"
-
-ICMP_TARGET = "8.8.8.8"
-
-
-class LeapNetworkChecker(object):
- """
- all network related checks
- """
- def __init__(self, *args, **kwargs):
- provider_gw = kwargs.pop('provider_gw', None)
- self.provider_gateway = provider_gw
-
- def run_all(self, checker=None):
- if not checker:
- checker = self
- #self.error = None # ?
-
- # for MVS
- checker.check_tunnel_default_interface()
- checker.check_internet_connection()
- checker.is_internet_up()
-
- if self.provider_gateway:
- checker.ping_gateway(self.provider_gateway)
-
- checker.parse_log_and_react([], ())
-
- def check_internet_connection(self):
- if _platform == "Linux":
- try:
- output = sh.ping("-c", "5", "-w", "5", ICMP_TARGET)
- # XXX should redirect this to netcheck logger.
- # and don't clutter main log.
- logger.debug('Network appears to be up.')
- except sh.ErrorReturnCode_1 as e:
- packet_loss = re.findall("\d+% packet loss", e.message)[0]
- logger.debug("Unidentified Connection Error: " + packet_loss)
- if not self.is_internet_up():
- error = "No valid internet connection found."
- else:
- error = "Provider server appears to be down."
-
- logger.error(error)
- raise exceptions.NoInternetConnection(error)
-
- else:
- raise NotImplementedError
-
- def is_internet_up(self):
- iface, gateway = self.get_default_interface_gateway()
- try:
- self.ping_gateway(self.provider_gateway)
- except exceptions.NoConnectionToGateway:
- return False
- return True
-
- def _get_route_table_linux(self):
- # do not use context manager, tests pass a StringIO
- f = open("/proc/net/route")
- route_table = f.readlines()
- f.close()
- #toss out header
- route_table.pop(0)
- if not route_table:
- raise exceptions.NoDefaultInterfaceFoundError
- return route_table
-
- def _get_def_iface_osx(self):
- default_iface = None
- #gateway = None
- routes = list(sh.route('-n', 'get', ICMP_TARGET, _iter=True))
- iface = filter(lambda l: "interface" in l, routes)
- if not iface:
- return None, None
- def_ifacel = re.findall('\w+\d', iface[0])
- default_iface = def_ifacel[0] if def_ifacel else None
- if not default_iface:
- return None, None
- _gw = filter(lambda l: "gateway" in l, routes)
- gw = re.findall('\d+\.\d+\.\d+\.\d+', _gw[0])[0]
- return default_iface, gw
-
- def _get_tunnel_iface_linux(self):
- # XXX review.
- # valid also when local router has a default entry?
- route_table = self._get_route_table_linux()
- line = route_table.pop(0)
- iface, destination = line.split('\t')[0:2]
- if not destination == '00000000' or not iface == 'tun0':
- raise exceptions.TunnelNotDefaultRouteError()
- return True
-
- def check_tunnel_default_interface(self):
- """
- Raises an TunnelNotDefaultRouteError
- if tun0 is not the chosen default route
- (including when no routes are present)
- """
- #logger.debug('checking tunnel default interface...')
-
- if _platform == "Linux":
- valid = self._get_tunnel_iface_linux()
- return valid
- elif _platform == "Darwin":
- default_iface, gw = self._get_def_iface_osx()
- #logger.debug('iface: %s', default_iface)
- if default_iface != "tun0":
- logger.debug('tunnel not default route! gw: %s', default_iface)
- # XXX should catch this and act accordingly...
- # but rather, this test should only be launched
- # when we have successfully completed a connection
- # ... TRIGGER: Connection stablished (or whatever it is)
- # in the logs
- raise exceptions.TunnelNotDefaultRouteError
- else:
- #logger.debug('PLATFORM !!! %s', _platform)
- raise NotImplementedError
-
- def _get_def_iface_linux(self):
- default_iface = None
- gateway = None
-
- route_table = self._get_route_table_linux()
- while route_table:
- line = route_table.pop(0)
- iface, destination, gateway = line.split('\t')[0:3]
- if destination == '00000000':
- default_iface = iface
- break
- return default_iface, gateway
-
- def get_default_interface_gateway(self):
- """
- gets the interface we are going thru.
- (this should be merged with check tunnel default interface,
- imo...)
- """
- if _platform == "Linux":
- default_iface, gw = self._get_def_iface_linux()
- elif _platform == "Darwin":
- default_iface, gw = self._get_def_iface_osx()
- else:
- raise NotImplementedError
-
- if not default_iface:
- raise exceptions.NoDefaultInterfaceFoundError
-
- if default_iface not in netifaces.interfaces():
- raise exceptions.InterfaceNotFoundError
- logger.debug('-- default iface %s', default_iface)
- return default_iface, gw
-
- def ping_gateway(self, gateway):
- # TODO: Discuss how much packet loss (%) is acceptable.
-
- # XXX -- validate gateway
- # -- is it a valid ip? (there's something in util)
- # -- is it a domain?
- # -- can we resolve? -- raise NoDNSError if not.
-
- # XXX -- sh.ping implemtation needs review!
- try:
- output = sh.ping("-c", "10", gateway).stdout
- except sh.ErrorReturnCode_1 as e:
- output = e.message
- finally:
- packet_loss = int(re.findall("(\d+)% packet loss", output)[0])
-
- logger.debug('packet loss %s%%' % packet_loss)
- if packet_loss > constants.MAX_ICMP_PACKET_LOSS:
- raise exceptions.NoConnectionToGateway
-
- def check_name_resolution(self, domain_name):
- try:
- socket.gethostbyname(domain_name)
- return True
- except socket.gaierror:
- raise exceptions.CannotResolveDomainError
-
- def parse_log_and_react(self, log, error_matrix=None):
- """
- compares the recent openvpn status log to
- strings passed in and executes the callbacks passed in.
- @param log: openvpn log
- @type log: list of strings
- @param error_matrix: tuples of strings and tuples of callbacks
- @type error_matrix: tuples strings and call backs
- """
- for line in log:
- # we could compile a regex here to save some cycles up -- kali
- for each in error_matrix:
- error, callbacks = each
- if error in line:
- for cb in callbacks:
- if callable(cb):
- cb()
diff --git a/src/leap/base/config.py b/src/leap/base/config.py
deleted file mode 100644
index 6a13db7d..00000000
--- a/src/leap/base/config.py
+++ /dev/null
@@ -1,348 +0,0 @@
-"""
-Configuration Base Class
-"""
-import grp
-import json
-import logging
-import re
-import socket
-import time
-import os
-
-logger = logging.getLogger(name=__name__)
-
-from dateutil import parser as dateparser
-from xdg import BaseDirectory
-import requests
-
-from leap.base import exceptions
-from leap.base import constants
-from leap.base.pluggableconfig import PluggableConfig
-from leap.util.fileutil import (mkdir_p)
-
-# move to base!
-from leap.eip import exceptions as eipexceptions
-
-
-class BaseLeapConfig(object):
- slug = None
-
- # XXX we have to enforce that every derived class
- # has a slug (via interface)
- # get property getter that raises NI..
-
- def save(self):
- raise NotImplementedError("abstract base class")
-
- def load(self):
- raise NotImplementedError("abstract base class")
-
- def get_config(self, *kwargs):
- raise NotImplementedError("abstract base class")
-
- @property
- def config(self):
- return self.get_config()
-
- def get_value(self, *kwargs):
- raise NotImplementedError("abstract base class")
-
-
-class MetaConfigWithSpec(type):
- """
- metaclass for JSONLeapConfig classes.
- It creates a configuration spec out of
- the `spec` dictionary. The `properties` attribute
- of the spec dict is turn into the `schema` attribute
- of the new class (which will be used to validate against).
- """
- # XXX in the near future, this is the
- # place where we want to enforce
- # singletons, read-only and similar stuff.
-
- def __new__(meta, classname, bases, classDict):
- schema_obj = classDict.get('spec', None)
-
- # not quite happy with this workaround.
- # I want to raise if missing spec dict, but only
- # for grand-children of this metaclass.
- # maybe should use abc module for this.
- abcderived = ("JSONLeapConfig",)
- if schema_obj is None and classname not in abcderived:
- raise exceptions.ImproperlyConfigured(
- "missing spec dict on your derived class (%s)" % classname)
-
- # we create a configuration spec attribute
- # from the spec dict
- config_class = type(
- classname + "Spec",
- (PluggableConfig, object),
- {'options': schema_obj})
- classDict['spec'] = config_class
-
- return type.__new__(meta, classname, bases, classDict)
-
-##########################################################
-# some hacking still in progress:
-
-# Configs have:
-
-# - a slug (from where a filename/folder is derived)
-# - a spec (for validation and defaults).
-# this spec is conformant to the json-schema.
-# basically a dict that will be used
-# for type casting and validation, and defaults settings.
-
-# all config objects, since they are derived from BaseConfig, implement basic
-# useful methods:
-# - save
-# - load
-
-##########################################################
-
-
-class JSONLeapConfig(BaseLeapConfig):
-
- __metaclass__ = MetaConfigWithSpec
-
- def __init__(self, *args, **kwargs):
- # sanity check
- try:
- assert self.slug is not None
- except AssertionError:
- raise exceptions.ImproperlyConfigured(
- "missing slug on JSONLeapConfig"
- " derived class")
- try:
- assert self.spec is not None
- except AssertionError:
- raise exceptions.ImproperlyConfigured(
- "missing spec on JSONLeapConfig"
- " derived class")
- assert issubclass(self.spec, PluggableConfig)
-
- self.domain = kwargs.pop('domain', None)
- self._config = self.spec(format="json")
- self._config.load()
- self.fetcher = kwargs.pop('fetcher', requests)
-
- # mandatory baseconfig interface
-
- def save(self, to=None, force=False):
- """
- force param will skip the dirty check.
- :type force: bool
- """
- # XXX this force=True does not feel to right
- # but still have to look for a better way
- # of dealing with dirtiness and the
- # trick of loading remote config only
- # when newer.
-
- if force:
- do_save = True
- else:
- do_save = self._config.is_dirty()
-
- if do_save:
- if to is None:
- to = self.filename
- folder, filename = os.path.split(to)
- if folder and not os.path.isdir(folder):
- mkdir_p(folder)
- self._config.serialize(to)
- return True
-
- else:
- return False
-
- def load(self, fromfile=None, from_uri=None, fetcher=None,
- force_download=False, verify=True):
-
- if from_uri is not None:
- fetched = self.fetch(
- from_uri,
- fetcher=fetcher,
- verify=verify,
- force_dl=force_download)
- if fetched:
- return
- if fromfile is None:
- fromfile = self.filename
- if os.path.isfile(fromfile):
- self._config.load(fromfile=fromfile)
- else:
- logger.error('tried to load config from non-existent path')
- logger.error('Not Found: %s', fromfile)
-
- def fetch(self, uri, fetcher=None, verify=True, force_dl=False):
- if not fetcher:
- fetcher = self.fetcher
-
- logger.debug('uri: %s (verify: %s)' % (uri, verify))
-
- rargs = (uri, )
- rkwargs = {'verify': verify}
- headers = {}
-
- curmtime = self.get_mtime() if not force_dl else None
- if curmtime:
- logger.debug('requesting with if-modified-since %s' % curmtime)
- headers['if-modified-since'] = curmtime
- rkwargs['headers'] = headers
-
- #request = fetcher.get(uri, verify=verify)
- request = fetcher.get(*rargs, **rkwargs)
- request.raise_for_status()
-
- if request.status_code == 304:
- logger.debug('...304 Not Changed')
- # On this point, we have to assume that
- # we HAD the filename. If that filename is corruct,
- # we should enforce a force_download in the load
- # method above.
- self._config.load(fromfile=self.filename)
- return True
-
- if request.json:
- mtime = None
- last_modified = request.headers.get('last-modified', None)
- if last_modified:
- _mtime = dateparser.parse(last_modified)
- mtime = int(_mtime.strftime("%s"))
- if callable(request.json):
- _json = request.json()
- else:
- # back-compat
- _json = request.json
- self._config.load(json.dumps(_json), mtime=mtime)
- self._config.set_dirty()
- else:
- # not request.json
- # might be server did not announce content properly,
- # let's try deserializing all the same.
- try:
- self._config.load(request.content)
- self._config.set_dirty()
- except ValueError:
- raise eipexceptions.LeapBadConfigFetchedError
-
- return True
-
- def get_mtime(self):
- try:
- _mtime = os.stat(self.filename)[8]
- mtime = time.strftime("%c GMT", time.gmtime(_mtime))
- return mtime
- except OSError:
- return None
-
- def get_config(self):
- return self._config.config
-
- # public methods
-
- def get_filename(self):
- return self._slug_to_filename()
-
- @property
- def filename(self):
- return self.get_filename()
-
- def validate(self, data):
- logger.debug('validating schema')
- self._config.validate(data)
- return True
-
- # private
-
- def _slug_to_filename(self):
- # is this going to work in winland if slug is "foo/bar" ?
- folder, filename = os.path.split(self.slug)
- config_file = get_config_file(filename, folder)
- return config_file
-
- def exists(self):
- return os.path.isfile(self.filename)
-
-
-#
-# utility functions
-#
-# (might be moved to some class as we see fit, but
-# let's remain functional for a while)
-# maybe base.config.util ??
-#
-
-
-def get_config_dir():
- """
- get the base dir for all leap config
- @rparam: config path
- @rtype: string
- """
- home = os.path.expanduser("~")
- if re.findall("leap_tests-[a-zA-Z0-9]{6}", home):
- # we're inside a test! :)
- return os.path.join(home, ".config/leap")
- else:
- # XXX dirspec is cross-platform,
- # we should borrow some of those
- # routines for osx/win and wrap this call.
- return os.path.join(BaseDirectory.xdg_config_home,
- 'leap')
-
-
-def get_config_file(filename, folder=None):
- """
- concatenates the given filename
- with leap config dir.
- @param filename: name of the file
- @type filename: string
- @rparam: full path to config file
- """
- path = []
- path.append(get_config_dir())
- if folder is not None:
- path.append(folder)
- path.append(filename)
- return os.path.join(*path)
-
-
-def get_default_provider_path():
- default_subpath = os.path.join("providers",
- constants.DEFAULT_PROVIDER)
- default_provider_path = get_config_file(
- '',
- folder=default_subpath)
- return default_provider_path
-
-
-def get_provider_path(domain):
- # XXX if not domain, return get_default_provider_path
- default_subpath = os.path.join("providers", domain)
- provider_path = get_config_file(
- '',
- folder=default_subpath)
- return provider_path
-
-
-def validate_ip(ip_str):
- """
- raises exception if the ip_str is
- not a valid representation of an ip
- """
- socket.inet_aton(ip_str)
-
-
-def get_username():
- try:
- return os.getlogin()
- except OSError as e:
- import pwd
- return pwd.getpwuid(os.getuid())[0]
-
-
-def get_groupname():
- gid = os.getgroups()[-1]
- return grp.getgrgid(gid).gr_name
diff --git a/src/leap/base/connection.py b/src/leap/base/connection.py
deleted file mode 100644
index 41d13935..00000000
--- a/src/leap/base/connection.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-Base Connection Classs
-"""
-from __future__ import (division, unicode_literals, print_function)
-
-import logging
-
-from leap.base.authentication import Authentication
-
-logger = logging.getLogger(name=__name__)
-
-
-class Connection(Authentication):
- # JSONLeapConfig
- #spec = {}
-
- def __init__(self, *args, **kwargs):
- self.connection_state = None
- self.desired_connection_state = None
- #XXX FIXME diamond inheritance gotcha..
- #If you inherit from >1 class,
- #super is only initializing one
- #of the bases..!!
- # I think we better pass config as a constructor
- # parameter -- kali 2012-08-30 04:33
- super(Connection, self).__init__(*args, **kwargs)
-
- def connect(self):
- """
- entry point for connection process
- """
- pass
-
- def disconnect(self):
- """
- disconnects client
- """
- 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_connection_state
-
- 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
-
-
-class ConnectionError(Exception):
- """
- generic connection error
- """
- def __str__(self):
- if len(self.args) >= 1:
- return repr(self.args[0])
- else:
- raise self()
-
-
-class UnrecoverableError(ConnectionError):
- """
- we cannot do anything about it, sorry
- """
- pass
diff --git a/src/leap/base/constants.py b/src/leap/base/constants.py
deleted file mode 100644
index f5665e5f..00000000
--- a/src/leap/base/constants.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""constants to be used in base module"""
-from leap import __branding
-APP_NAME = __branding.get("short_name", "leap-client")
-OPENVPN_BIN = "openvpn"
-
-# default provider placeholder
-# using `example.org` we make sure that this
-# is not going to be resolved during the tests phases
-# (we expect testers to add it to their /etc/hosts
-
-DEFAULT_PROVIDER = __branding.get(
- "provider_domain",
- "testprovider.example.org")
-
-DEFINITION_EXPECTED_PATH = "provider.json"
-
-DEFAULT_PROVIDER_DEFINITION = {
- u"api_uri": "https://api.%s/" % DEFAULT_PROVIDER,
- u"api_version": u"1",
- u"ca_cert_fingerprint": "SHA256: fff",
- u"ca_cert_uri": u"https://%s/ca.crt" % DEFAULT_PROVIDER,
- u"default_language": u"en",
- u"description": {
- u"en": u"A demonstration service provider using the LEAP platform"
- },
- u"domain": "%s" % DEFAULT_PROVIDER,
- u"enrollment_policy": u"open",
- u"languages": [
- u"en"
- ],
- u"name": {
- u"en": u"Test Provider"
- },
- u"services": [
- "openvpn"
- ]
-}
-
-
-MAX_ICMP_PACKET_LOSS = 10
-
-ROUTE_CHECK_INTERVAL = 10
diff --git a/src/leap/base/exceptions.py b/src/leap/base/exceptions.py
deleted file mode 100644
index 2e31b33b..00000000
--- a/src/leap/base/exceptions.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""
-Exception attributes and their meaning/uses
--------------------------------------------
-
-* critical: if True, will abort execution prematurely,
- after attempting any cleaning
- action.
-
-* failfirst: breaks any error_check loop that is examining
- the error queue.
-
-* message: the message that will be used in the __repr__ of the exception.
-
-* usermessage: the message that will be passed to user in ErrorDialogs
- in Qt-land.
-"""
-from leap.util.translations import translate
-
-
-class LeapException(Exception):
- """
- base LeapClient exception
- sets some parameters that we will check
- during error checking routines
- """
-
- critical = False
- failfirst = False
- warning = False
-
-
-class CriticalError(LeapException):
- """
- we cannot do anything about it
- """
- critical = True
- failfirst = True
-
-
-# In use ???
-# don't thing so. purge if not...
-
-class MissingConfigFileError(Exception):
- pass
-
-
-class ImproperlyConfigured(Exception):
- pass
-
-
-# NOTE: "Errors" (context) has to be a explicit string!
-
-
-class InterfaceNotFoundError(LeapException):
- # XXX should take iface arg on init maybe?
- message = "interface not found"
- usermessage = translate(
- "Errors",
- "Interface not found")
-
-
-class NoDefaultInterfaceFoundError(LeapException):
- message = "no default interface found"
- usermessage = translate(
- "Errors",
- "Looks like your computer "
- "is not connected to the internet")
-
-
-class NoConnectionToGateway(CriticalError):
- message = "no connection to gateway"
- usermessage = translate(
- "Errors",
- "Looks like there are problems "
- "with your internet connection")
-
-
-class NoInternetConnection(CriticalError):
- message = "No Internet connection found"
- usermessage = translate(
- "Errors",
- "It looks like there is no internet connection.")
- # and now we try to connect to our web to troubleshoot LOL :P
-
-
-class CannotResolveDomainError(LeapException):
- message = "Cannot resolve domain"
- usermessage = translate(
- "Errors",
- "Domain cannot be found")
-
-
-class TunnelNotDefaultRouteError(LeapException):
- message = "Tunnel connection dissapeared. VPN down?"
- usermessage = translate(
- "Errors",
- "The Encrypted Connection was lost.")
diff --git a/src/leap/base/network.py b/src/leap/base/network.py
deleted file mode 100644
index d841e692..00000000
--- a/src/leap/base/network.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import (print_function)
-import logging
-import threading
-
-from leap.eip import config as eipconfig
-from leap.base.checks import LeapNetworkChecker
-from leap.base.constants import ROUTE_CHECK_INTERVAL
-from leap.base.exceptions import TunnelNotDefaultRouteError
-from leap.util.misc import null_check
-from leap.util.coroutines import (launch_thread, process_events)
-
-from time import sleep
-
-logger = logging.getLogger(name=__name__)
-
-
-class NetworkCheckerThread(object):
- """
- Manages network checking thread that makes sure we have a working network
- connection.
- """
- def __init__(self, *args, **kwargs):
-
- self.status_signals = kwargs.pop('status_signals', None)
- self.error_cb = kwargs.pop(
- 'error_cb',
- lambda exc: logger.error("%s", exc.message))
- self.shutdown = threading.Event()
-
- # XXX get provider passed here
- provider = kwargs.pop('provider', None)
- null_check(provider, 'provider')
-
- eipconf = eipconfig.EIPConfig(domain=provider)
- eipconf.load()
- eipserviceconf = eipconfig.EIPServiceConfig(domain=provider)
- eipserviceconf.load()
-
- gw = eipconfig.get_eip_gateway(
- eipconfig=eipconf,
- eipserviceconfig=eipserviceconf)
- self.checker = LeapNetworkChecker(
- provider_gw=gw)
-
- def start(self):
- self.process_handle = self._launch_recurrent_network_checks(
- (self.error_cb,))
-
- def stop(self):
- self.process_handle.join(timeout=0.1)
- self.shutdown.set()
- logger.debug("network checked stopped.")
-
- def run_checks(self):
- pass
-
- #private methods
-
- #here all the observers in fail_callbacks expect one positional argument,
- #which is exception so we can try by passing a lambda with logger to
- #check it works.
-
- def _network_checks_thread(self, fail_callbacks):
- #TODO: replace this with waiting for a signal from openvpn
- while True:
- try:
- self.checker.check_tunnel_default_interface()
- break
- except TunnelNotDefaultRouteError:
- # XXX ??? why do we sleep here???
- # aa: If the openvpn isn't up and running yet,
- # let's give it a moment to breath.
- #logger.error('NOT DEFAULT ROUTE!----')
- # Instead of this, we should flag when the
- # iface IS SUPPOSED to be up imo. -- kali
- sleep(1)
-
- fail_observer_dict = dict(((
- observer,
- process_events(observer)) for observer in fail_callbacks))
-
- while not self.shutdown.is_set():
- try:
- self.checker.check_tunnel_default_interface()
- self.checker.check_internet_connection()
- sleep(ROUTE_CHECK_INTERVAL)
- except Exception as exc:
- for obs in fail_observer_dict:
- fail_observer_dict[obs].send(exc)
- sleep(ROUTE_CHECK_INTERVAL)
-
- #reset event
- # I see a problem with this. You cannot stop it, it
- # resets itself forever. -- kali
-
- # XXX use QTimer for the recurrent triggers,
- # and ditch the sleeps.
- logger.debug('resetting event')
- self.shutdown.clear()
-
- def _launch_recurrent_network_checks(self, fail_callbacks):
- # XXX reimplement using QTimer -- kali
- watcher = launch_thread(
- self._network_checks_thread,
- (fail_callbacks,))
- return watcher
diff --git a/src/leap/base/pluggableconfig.py b/src/leap/base/pluggableconfig.py
deleted file mode 100644
index 3517db6b..00000000
--- a/src/leap/base/pluggableconfig.py
+++ /dev/null
@@ -1,455 +0,0 @@
-"""
-generic configuration handlers
-"""
-import copy
-import json
-import logging
-import os
-import time
-import urlparse
-
-import jsonschema
-
-from leap.util.translations import LEAPTranslatable
-
-logger = logging.getLogger(__name__)
-
-
-__all__ = ['PluggableConfig',
- 'adaptors',
- 'types',
- 'UnknownOptionException',
- 'MissingValueException',
- 'ConfigurationProviderException',
- 'TypeCastException']
-
-# exceptions
-
-
-class UnknownOptionException(Exception):
- """exception raised when a non-configuration
- value is present in the configuration"""
-
-
-class MissingValueException(Exception):
- """exception raised when a required value is missing"""
-
-
-class ConfigurationProviderException(Exception):
- """exception raised when a configuration provider is missing, etc"""
-
-
-class TypeCastException(Exception):
- """exception raised when a
- configuration item cannot be coerced to a type"""
-
-
-class ConfigAdaptor(object):
- """
- abstract base class for config adaotors for
- serialization/deserialization and custom validation
- and type casting.
- """
- def read(self, filename):
- raise NotImplementedError("abstract base class")
-
- def write(self, config, filename):
- with open(filename, 'w') as f:
- self._write(f, config)
-
- def _write(self, fp, config):
- raise NotImplementedError("abstract base class")
-
- def validate(self, config, schema):
- raise NotImplementedError("abstract base class")
-
-
-adaptors = {}
-
-
-class JSONSchemaEncoder(json.JSONEncoder):
- """
- custom default encoder that
- casts python objects to json objects for
- the schema validation
- """
- def default(self, obj):
- if obj is str:
- return 'string'
- if obj is unicode:
- return 'string'
- if obj is int:
- return 'integer'
- if obj is list:
- return 'array'
- if obj is dict:
- return 'object'
- if obj is bool:
- return 'boolean'
-
-
-class JSONAdaptor(ConfigAdaptor):
- indent = 2
- extensions = ['json']
-
- def read(self, _from):
- if isinstance(_from, file):
- _from_string = _from.read()
- if isinstance(_from, str):
- _from_string = _from
- return json.loads(_from_string)
-
- def _write(self, fp, config):
- fp.write(json.dumps(config,
- indent=self.indent,
- sort_keys=True))
-
- def validate(self, config, schema_obj):
- schema_json = JSONSchemaEncoder().encode(schema_obj)
- schema = json.loads(schema_json)
- jsonschema.validate(config, schema)
-
-
-adaptors['json'] = JSONAdaptor()
-
-#
-# Adaptors
-#
-# Allow to apply a predefined set of types to the
-# specs, so it checks the validity of formats and cast it
-# to proper python types.
-
-# TODO:
-# - HTTPS uri
-
-
-class DateType(object):
- fmt = '%Y-%m-%d'
-
- def to_python(self, data):
- return time.strptime(data, self.fmt)
-
- def get_prep_value(self, data):
- return time.strftime(self.fmt, data)
-
-
-class TranslatableType(object):
- """
- a type that casts to LEAPTranslatable objects.
- Used for labels we get from providers and stuff.
- """
-
- def to_python(self, data):
- return LEAPTranslatable(data)
-
- # needed? we already have an extended dict...
- #def get_prep_value(self, data):
- #return dict(data)
-
-
-class URIType(object):
-
- def to_python(self, data):
- parsed = urlparse.urlparse(data)
- if not parsed.scheme:
- raise TypeCastException("uri %s has no schema" % data)
- return parsed
-
- def get_prep_value(self, data):
- return data.geturl()
-
-
-class HTTPSURIType(object):
-
- def to_python(self, data):
- parsed = urlparse.urlparse(data)
- if not parsed.scheme:
- raise TypeCastException("uri %s has no schema" % data)
- if parsed.scheme != "https":
- raise TypeCastException(
- "uri %s does not has "
- "https schema" % data)
- return parsed
-
- def get_prep_value(self, data):
- return data.geturl()
-
-
-types = {
- 'date': DateType(),
- 'uri': URIType(),
- 'https-uri': HTTPSURIType(),
- 'translatable': TranslatableType(),
-}
-
-
-class PluggableConfig(object):
-
- options = {}
-
- def __init__(self,
- adaptors=adaptors,
- types=types,
- format=None):
-
- self.config = {}
- self.adaptors = adaptors
- self.types = types
- self._format = format
- self.mtime = None
- self.dirty = False
-
- @property
- def option_dict(self):
- if hasattr(self, 'options') and isinstance(self.options, dict):
- return self.options.get('properties', None)
-
- def items(self):
- """
- act like an iterator
- """
- if isinstance(self.option_dict, dict):
- return self.option_dict.items()
- return self.options
-
- def validate(self, config, format=None):
- """
- validate config
- """
- schema = self.options
- if format is None:
- format = self._format
-
- if format:
- adaptor = self.get_adaptor(self._format)
- adaptor.validate(config, schema)
- else:
- # we really should make format mandatory...
- logger.error('no format passed to validate')
-
- # first round of validation is ok.
- # now we proceed to cast types if any specified.
- self.to_python(config)
-
- def to_python(self, config):
- """
- cast types following first type and then format indications.
- """
- unseen_options = [i for i in config if i not in self.option_dict]
- if unseen_options:
- raise UnknownOptionException(
- "Unknown options: %s" % ', '.join(unseen_options))
-
- for key, value in config.items():
- _type = self.option_dict[key].get('type')
- if _type is None and 'default' in self.option_dict[key]:
- _type = type(self.option_dict[key]['default'])
- if _type is not None:
- tocast = True
- if not callable(_type) and isinstance(value, _type):
- tocast = False
- if tocast:
- try:
- config[key] = _type(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not coerce %s, %s, "
- "to type %s: %s" % (key, value, _type.__name__, e))
- _format = self.option_dict[key].get('format', None)
- _ftype = self.types.get(_format, None)
- if _ftype:
- try:
- config[key] = _ftype.to_python(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not coerce %s, %s, "
- "to format %s: %s" % (key, value,
- _ftype.__class__.__name__,
- e))
-
- return config
-
- def prep_value(self, config):
- """
- the inverse of to_python method,
- called just before serialization
- """
- for key, value in config.items():
- _format = self.option_dict[key].get('format', None)
- _ftype = self.types.get(_format, None)
- if _ftype and hasattr(_ftype, 'get_prep_value'):
- try:
- config[key] = _ftype.get_prep_value(value)
- except BaseException, e:
- raise TypeCastException(
- "Could not serialize %s, %s, "
- "by format %s: %s" % (key, value,
- _ftype.__class__.__name__,
- e))
- else:
- config[key] = value
- return config
-
- # methods for adding configuration
-
- def get_default_values(self):
- """
- return a config options from configuration defaults
- """
- defaults = {}
- for key, value in self.items():
- if 'default' in value:
- defaults[key] = value['default']
- return copy.deepcopy(defaults)
-
- def get_adaptor(self, format):
- """
- get specified format adaptor or
- guess for a given filename
- """
- adaptor = self.adaptors.get(format, None)
- if adaptor:
- return adaptor
-
- # not registered in adaptors dict, let's try all
- for adaptor in self.adaptors.values():
- if format in adaptor.extensions:
- return adaptor
-
- def filename2format(self, filename):
- extension = os.path.splitext(filename)[-1]
- return extension.lstrip('.') or None
-
- def serialize(self, filename, format=None, full=False):
- if not format:
- format = self._format
- if not format:
- format = self.filename2format(filename)
- if not format:
- raise Exception('Please specify a format')
- # TODO: more specific exception type
-
- adaptor = self.get_adaptor(format)
- if not adaptor:
- raise Exception("Adaptor not found for format: %s" % format)
-
- config = copy.deepcopy(self.config)
- serializable = self.prep_value(config)
- adaptor.write(serializable, filename)
-
- if self.mtime:
- self.touch_mtime(filename)
-
- def touch_mtime(self, filename):
- mtime = self.mtime
- os.utime(filename, (mtime, mtime))
-
- def deserialize(self, string=None, fromfile=None, format=None):
- """
- load configuration from a file or string
- """
-
- def _try_deserialize():
- if fromfile:
- with open(fromfile, 'r') as f:
- content = adaptor.read(f)
- elif string:
- content = adaptor.read(string)
- return content
-
- # XXX cleanup this!
-
- if fromfile:
- assert os.path.exists(fromfile)
- if not format:
- format = self.filename2format(fromfile)
-
- if not format:
- format = self._format
- if format:
- adaptor = self.get_adaptor(format)
- else:
- adaptor = None
-
- if adaptor:
- content = _try_deserialize()
- return content
-
- # no adaptor, let's try rest of adaptors
-
- adaptors = self.adaptors[:]
-
- if format:
- adaptors.sort(
- key=lambda x: int(
- format in x.extensions),
- reverse=True)
-
- for adaptor in adaptors:
- content = _try_deserialize()
- return content
-
- def set_dirty(self):
- self.dirty = True
-
- def is_dirty(self):
- return self.dirty
-
- def load(self, *args, **kwargs):
- """
- load from string or file
- if no string of fromfile option is given,
- it will attempt to load from defaults
- defined in the schema.
- """
- string = args[0] if args else None
- fromfile = kwargs.get("fromfile", None)
- mtime = kwargs.pop("mtime", None)
- self.mtime = mtime
- content = None
-
- # start with defaults, so we can
- # have partial values applied.
- content = self.get_default_values()
- if string and isinstance(string, str):
- content = self.deserialize(string)
-
- if not string and fromfile is not None:
- #import ipdb;ipdb.set_trace()
- content = self.deserialize(fromfile=fromfile)
-
- if not content:
- logger.error('no content could be loaded')
- # XXX raise!
- return
-
- # lazy evaluation until first level of nesting
- # to allow lambdas with context-dependant info
- # like os.path.expanduser
- for k, v in content.iteritems():
- if callable(v):
- content[k] = v()
-
- self.validate(content)
- self.config = content
- return True
-
-
-def testmain(): # pragma: no cover
-
- from tests import test_validation as t
- import pprint
-
- config = PluggableConfig(_format="json")
- properties = copy.deepcopy(t.sample_spec)
-
- config.options = properties
- config.load(fromfile='data.json')
-
- print 'config'
- pprint.pprint(config.config)
-
- config.serialize('/tmp/testserial.json')
-
-if __name__ == "__main__":
- testmain()
diff --git a/src/leap/base/providers.py b/src/leap/base/providers.py
deleted file mode 100644
index d41f3695..00000000
--- a/src/leap/base/providers.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""all dealing with leap-providers: definition files, updating"""
-from leap.base import config as baseconfig
-from leap.base import specs
-
-
-class LeapProviderDefinition(baseconfig.JSONLeapConfig):
- spec = specs.leap_provider_spec
-
- def _get_slug(self):
- domain = getattr(self, 'domain', None)
- if domain:
- path = baseconfig.get_provider_path(domain)
- else:
- path = baseconfig.get_default_provider_path()
-
- return baseconfig.get_config_file(
- 'provider.json', folder=path)
-
- def _set_slug(self, *args, **kwargs):
- raise AttributeError("you cannot set slug")
-
- slug = property(_get_slug, _set_slug)
-
-
-class LeapProviderSet(object):
- # we gather them from the filesystem
- # TODO: (MVS+)
- def __init__(self):
- self.count = 0
diff --git a/src/leap/base/specs.py b/src/leap/base/specs.py
deleted file mode 100644
index f57d7e9c..00000000
--- a/src/leap/base/specs.py
+++ /dev/null
@@ -1,67 +0,0 @@
-leap_provider_spec = {
- 'description': 'provider definition',
- 'type': 'object',
- 'properties': {
- #'serial': {
- #'type': int,
- #'default': 1,
- #'required': True,
- #},
- 'version': {
- 'type': unicode,
- 'default': '0.1.0'
- #'required': True
- },
- "default_language": {
- 'type': unicode,
- 'default': 'en'
- },
- 'domain': {
- 'type': unicode, # XXX define uri type
- 'default': 'testprovider.example.org'
- #'required': True,
- },
- 'name': {
- #'type': LEAPTranslatable,
- 'type': dict,
- 'format': 'translatable',
- 'default': {u'en': u'Test Provider'}
- #'required': True
- },
- 'description': {
- #'type': LEAPTranslatable,
- 'type': dict,
- 'format': 'translatable',
- 'default': {u'en': u'Test provider'}
- },
- 'enrollment_policy': {
- 'type': unicode, # oneof ??
- 'default': 'open'
- },
- 'services': {
- 'type': list, # oneof ??
- 'default': ['eip']
- },
- 'api_version': {
- 'type': unicode,
- 'default': '0.1.0' # version regexp
- },
- 'api_uri': {
- 'type': unicode # uri
- },
- 'public_key': {
- 'type': unicode # fingerprint
- },
- 'ca_cert_fingerprint': {
- 'type': unicode,
- },
- 'ca_cert_uri': {
- 'type': unicode,
- 'format': 'https-uri'
- },
- 'languages': {
- 'type': list,
- 'default': ['en']
- }
- }
-}
diff --git a/src/leap/base/tests/__init__.py b/src/leap/base/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/base/tests/__init__.py
+++ /dev/null
diff --git a/src/leap/base/tests/test_auth.py b/src/leap/base/tests/test_auth.py
deleted file mode 100644
index b3009a9b..00000000
--- a/src/leap/base/tests/test_auth.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from BaseHTTPServer import BaseHTTPRequestHandler
-import urlparse
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-import requests
-#from mock import Mock
-
-from leap.base import auth
-#from leap.base import exceptions
-from leap.eip.tests.test_checks import NoLogRequestHandler
-from leap.testing.basetest import BaseLeapTest
-from leap.testing.https_server import BaseHTTPSServerTestCase
-
-
-class LeapSRPRegisterTests(BaseHTTPSServerTestCase, BaseLeapTest):
- __name__ = "leap_srp_register_test"
- provider = "testprovider.example.org"
-
- class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
- responses = {
- '/': ['OK', '']}
-
- def do_GET(self):
- path = urlparse.urlparse(self.path)
- message = '\n'.join(self.responses.get(
- path.path, None))
- self.send_response(200)
- self.end_headers()
- self.wfile.write(message)
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_srp_auth_should_implement_check_methods(self):
- SERVER = "https://localhost:8443"
- srp_auth = auth.LeapSRPRegister(provider=SERVER, verify=False)
-
- self.assertTrue(hasattr(srp_auth, "init_session"),
- "missing meth")
- self.assertTrue(hasattr(srp_auth, "get_registration_uri"),
- "missing meth")
- self.assertTrue(hasattr(srp_auth, "register_user"),
- "missing meth")
-
- def test_srp_auth_basic_functionality(self):
- SERVER = "https://localhost:8443"
- srp_auth = auth.LeapSRPRegister(provider=SERVER, verify=False)
-
- self.assertIsInstance(srp_auth.session, requests.sessions.Session)
- self.assertEqual(
- srp_auth.get_registration_uri(),
- "https://localhost:8443/1/users")
diff --git a/src/leap/base/tests/test_checks.py b/src/leap/base/tests/test_checks.py
deleted file mode 100644
index 8126755b..00000000
--- a/src/leap/base/tests/test_checks.py
+++ /dev/null
@@ -1,177 +0,0 @@
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-import os
-import sh
-
-from mock import (patch, Mock)
-from StringIO import StringIO
-
-from leap.base import checks
-from leap.base import exceptions
-from leap.testing.basetest import BaseLeapTest
-
-_uid = os.getuid()
-
-
-class LeapNetworkCheckTest(BaseLeapTest):
- __name__ = "leap_network_check_tests"
-
- def setUp(self):
- os.environ['PATH'] += ':/bin'
- pass
-
- def tearDown(self):
- pass
-
- def test_checker_should_implement_check_methods(self):
- checker = checks.LeapNetworkChecker()
-
- self.assertTrue(hasattr(checker, "check_internet_connection"),
- "missing meth")
- self.assertTrue(hasattr(checker, "check_tunnel_default_interface"),
- "missing meth")
- self.assertTrue(hasattr(checker, "is_internet_up"),
- "missing meth")
- self.assertTrue(hasattr(checker, "ping_gateway"),
- "missing meth")
- self.assertTrue(hasattr(checker, "parse_log_and_react"),
- "missing meth")
-
- def test_checker_should_actually_call_all_tests(self):
- checker = checks.LeapNetworkChecker()
- mc = Mock()
- checker.run_all(checker=mc)
- self.assertTrue(mc.check_internet_connection.called, "not called")
- self.assertTrue(mc.check_tunnel_default_interface.called, "not called")
- self.assertTrue(mc.is_internet_up.called, "not called")
- self.assertTrue(mc.parse_log_and_react.called, "not called")
-
- # ping gateway only called if we pass provider_gw
- checker = checks.LeapNetworkChecker(provider_gw="0.0.0.0")
- mc = Mock()
- checker.run_all(checker=mc)
- self.assertTrue(mc.check_internet_connection.called, "not called")
- self.assertTrue(mc.check_tunnel_default_interface.called, "not called")
- self.assertTrue(mc.ping_gateway.called, "not called")
- self.assertTrue(mc.is_internet_up.called, "not called")
- self.assertTrue(mc.parse_log_and_react.called, "not called")
-
- def test_get_default_interface_no_interface(self):
- checker = checks.LeapNetworkChecker()
- with patch('leap.base.checks.open', create=True) as mock_open:
- with self.assertRaises(exceptions.NoDefaultInterfaceFoundError):
- mock_open.return_value = StringIO(
- "Iface\tDestination Gateway\t"
- "Flags\tRefCntd\tUse\tMetric\t"
- "Mask\tMTU\tWindow\tIRTT")
- checker.get_default_interface_gateway()
-
- def test_check_tunnel_default_interface(self):
- checker = checks.LeapNetworkChecker()
- with patch('leap.base.checks.open', create=True) as mock_open:
- with self.assertRaises(exceptions.TunnelNotDefaultRouteError):
- mock_open.return_value = StringIO(
- "Iface\tDestination Gateway\t"
- "Flags\tRefCntd\tUse\tMetric\t"
- "Mask\tMTU\tWindow\tIRTT\n"
- "wlan0\t00000000\t0102A8C0\t"
- "0003\t0\t0\t0\t00000000\t0\t0\t0")
- checker.check_tunnel_default_interface()
-
- with patch('leap.base.checks.open', create=True) as mock_open:
- mock_open.return_value = StringIO(
- "Iface\tDestination Gateway\t"
- "Flags\tRefCntd\tUse\tMetric\t"
- "Mask\tMTU\tWindow\tIRTT\n"
- "tun0\t00000000\t01002A0A\t0003\t0\t0\t0\t00000080\t0\t0\t0")
- checker.check_tunnel_default_interface()
-
- def test_ping_gateway_fail(self):
- checker = checks.LeapNetworkChecker()
- with patch.object(sh, "ping") as mocked_ping:
- with self.assertRaises(exceptions.NoConnectionToGateway):
- mocked_ping.return_value = Mock
- mocked_ping.return_value.stdout = "11% packet loss"
- checker.ping_gateway("4.2.2.2")
-
- def test_ping_gateway(self):
- checker = checks.LeapNetworkChecker()
- with patch.object(sh, "ping") as mocked_ping:
- mocked_ping.return_value = Mock
- mocked_ping.return_value.stdout = """
-PING 4.2.2.2 (4.2.2.2) 56(84) bytes of data.
-64 bytes from 4.2.2.2: icmp_req=1 ttl=54 time=33.8 ms
-64 bytes from 4.2.2.2: icmp_req=2 ttl=54 time=30.6 ms
-64 bytes from 4.2.2.2: icmp_req=3 ttl=54 time=31.4 ms
-64 bytes from 4.2.2.2: icmp_req=4 ttl=54 time=36.1 ms
-64 bytes from 4.2.2.2: icmp_req=5 ttl=54 time=30.8 ms
-64 bytes from 4.2.2.2: icmp_req=6 ttl=54 time=30.4 ms
-64 bytes from 4.2.2.2: icmp_req=7 ttl=54 time=30.7 ms
-64 bytes from 4.2.2.2: icmp_req=8 ttl=54 time=32.7 ms
-64 bytes from 4.2.2.2: icmp_req=9 ttl=54 time=31.4 ms
-64 bytes from 4.2.2.2: icmp_req=10 ttl=54 time=33.3 ms
-
---- 4.2.2.2 ping statistics ---
-10 packets transmitted, 10 received, 0% packet loss, time 9016ms
-rtt min/avg/max/mdev = 30.497/32.172/36.161/1.755 ms"""
- checker.ping_gateway("4.2.2.2")
-
- def test_check_internet_connection_failures(self):
- checker = checks.LeapNetworkChecker()
- TimeoutError = get_ping_timeout_error()
- with patch.object(sh, "ping") as mocked_ping:
- mocked_ping.side_effect = TimeoutError
- with self.assertRaises(exceptions.NoInternetConnection):
- with patch.object(checker, "ping_gateway") as mock_gateway:
- mock_gateway.side_effect = exceptions.NoConnectionToGateway
- checker.check_internet_connection()
-
- with patch.object(sh, "ping") as mocked_ping:
- mocked_ping.side_effect = TimeoutError
- with self.assertRaises(exceptions.NoInternetConnection):
- with patch.object(checker, "ping_gateway") as mock_gateway:
- mock_gateway.return_value = True
- checker.check_internet_connection()
-
- def test_parse_log_and_react(self):
- checker = checks.LeapNetworkChecker()
- to_call = Mock()
- log = [("leap.openvpn - INFO - Mon Nov 19 13:36:24 2012 "
- "read UDPv4 [ECONNREFUSED]: Connection refused (code=111)")]
- err_matrix = [(checks.EVENT_CONNECT_REFUSED, (to_call, ))]
- checker.parse_log_and_react(log, err_matrix)
- self.assertTrue(to_call.called)
-
- log = [("2012-11-19 13:36:26,177 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command "
- "failed: external program exited"),
- ("2012-11-19 13:36:26,178 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command "
- "failed: external program exited"),
- ("2012-11-19 13:36:26,180 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command "
- "failed: external program exited"),
- ("2012-11-19 13:36:26,181 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:24 2012 /sbin/ifconfig tun0 0.0.0.0"),
- ("2012-11-19 13:36:26,182 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:24 2012 Linux ip addr del failed: external "
- "program exited with error stat"),
- ("2012-11-19 13:36:26,183 - leap.openvpn - INFO - "
- "Mon Nov 19 13:36:26 2012 SIGTERM[hard,] received, process"
- "exiting"), ]
- to_call.reset_mock()
- checker.parse_log_and_react(log, err_matrix)
- self.assertFalse(to_call.called)
-
- to_call.reset_mock()
- checker.parse_log_and_react([], err_matrix)
- self.assertFalse(to_call.called)
-
-
-def get_ping_timeout_error():
- try:
- sh.ping("-c", "1", "-w", "1", "8.8.7.7")
- except Exception as e:
- return e
diff --git a/src/leap/base/tests/test_config.py b/src/leap/base/tests/test_config.py
deleted file mode 100644
index d03149b2..00000000
--- a/src/leap/base/tests/test_config.py
+++ /dev/null
@@ -1,247 +0,0 @@
-import json
-import os
-import platform
-import socket
-#import tempfile
-
-import mock
-import requests
-
-from leap.base import config
-from leap.base import constants
-from leap.base import exceptions
-from leap.eip import constants as eipconstants
-from leap.util.fileutil import mkdir_p
-from leap.testing.basetest import BaseLeapTest
-
-
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-_system = platform.system()
-
-
-class JSONLeapConfigTest(BaseLeapTest):
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_metaclass(self):
- with self.assertRaises(exceptions.ImproperlyConfigured) as exc:
- class DummyTestConfig(config.JSONLeapConfig):
- __metaclass__ = config.MetaConfigWithSpec
- exc.startswith("missing spec dict")
-
- class DummyTestConfig(config.JSONLeapConfig):
- __metaclass__ = config.MetaConfigWithSpec
- spec = {'properties': {}}
- with self.assertRaises(exceptions.ImproperlyConfigured) as exc:
- DummyTestConfig()
- exc.startswith("missing slug")
-
- class DummyTestConfig(config.JSONLeapConfig):
- __metaclass__ = config.MetaConfigWithSpec
- spec = {'properties': {}}
- slug = "foo"
- DummyTestConfig()
-
-######################################3
-#
-# provider fetch tests block
-#
-
-
-class ProviderTest(BaseLeapTest):
- # override per test fixtures
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
-
-# XXX depreacated. similar test in eip.checks
-
-#class BareHomeTestCase(ProviderTest):
-#
- #__name__ = "provider_config_tests_bare_home"
-#
- #def test_should_raise_if_missing_eip_json(self):
- #with self.assertRaises(exceptions.MissingConfigFileError):
- #config.get_config_json(os.path.join(self.home, 'eip.json'))
-
-
-class ProviderDefinitionTestCase(ProviderTest):
- # XXX MOVE TO eip.test_checks
- # -- kali 2012-08-24 00:38
-
- __name__ = "provider_config_tests"
-
- def setUp(self):
- # dump a sample eip file
- # XXX Move to Use EIP Spec Instead!!!
- # XXX tests to be moved to eip.checks and eip.providers
- # XXX can use eipconfig.dump_default_eipconfig
-
- path = os.path.join(self.home, '.config', 'leap')
- mkdir_p(path)
- with open(os.path.join(path, 'eip.json'), 'w') as fp:
- json.dump(eipconstants.EIP_SAMPLE_JSON, fp)
-
-
-# these tests below should move to
-# eip.checks
-# config.Configuration has been deprecated
-
-# TODO:
-# - We're instantiating a ProviderTest because we're doing the home wipeoff
-# on setUpClass instead of the setUp (for speedup of the general cases).
-
-# We really should be testing all of them in the same testCase, and
-# doing an extra wipe of the tempdir... but be careful!!!! do not mess with
-# os.environ home more than needed... that could potentially bite!
-
-# XXX actually, another thing to fix here is separating tests:
-# - test that requests has been called.
-# - check deeper for error types/msgs
-
-# we SHOULD inject requests dep in the constructor
-# (so we can pass mock easily).
-
-
-#class ProviderFetchConError(ProviderTest):
- #def test_connection_error(self):
- #with mock.patch.object(requests, "get") as mock_method:
- #mock_method.side_effect = requests.ConnectionError
- #cf = config.Configuration()
- #self.assertIsInstance(cf.error, str)
-#
-#
-#class ProviderFetchHttpError(ProviderTest):
- #def test_file_not_found(self):
- #with mock.patch.object(requests, "get") as mock_method:
- #mock_method.side_effect = requests.HTTPError
- #cf = config.Configuration()
- #self.assertIsInstance(cf.error, str)
-#
-#
-#class ProviderFetchInvalidUrl(ProviderTest):
- #def test_invalid_url(self):
- #cf = config.Configuration("ht")
- #self.assertTrue(cf.error)
-
-
-# end provider fetch tests
-###########################################
-
-
-class ConfigHelperFunctions(BaseLeapTest):
-
- __name__ = "config_helper_tests"
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- # tests
-
- @unittest.skipUnless(_system == "Linux", "linux only")
- def test_lin_get_config_file(self):
- """
- config file path where expected? (linux)
- """
- self.assertEqual(
- config.get_config_file(
- 'test', folder="foo/bar"),
- os.path.expanduser(
- '~/.config/leap/foo/bar/test')
- )
-
- @unittest.skipUnless(_system == "Darwin", "mac only")
- def test_mac_get_config_file(self):
- """
- config file path where expected? (mac)
- """
- self._missing_test_for_plat(do_raise=True)
-
- @unittest.skipUnless(_system == "Windows", "win only")
- def test_win_get_config_file(self):
- """
- config file path where expected?
- """
- self._missing_test_for_plat(do_raise=True)
-
- #
- # XXX hey, I'm raising exceptions here
- # on purpose. just wanted to make sure
- # that the skip stuff is doing it right.
- # If you're working on win/macos tests,
- # feel free to remove tests that you see
- # are too redundant.
-
- @unittest.skipUnless(_system == "Linux", "linux only")
- def test_lin_get_config_dir(self):
- """
- nice config dir? (linux)
- """
- self.assertEqual(
- config.get_config_dir(),
- os.path.expanduser('~/.config/leap'))
-
- @unittest.skipUnless(_system == "Darwin", "mac only")
- def test_mac_get_config_dir(self):
- """
- nice config dir? (mac)
- """
- self._missing_test_for_plat(do_raise=True)
-
- @unittest.skipUnless(_system == "Windows", "win only")
- def test_win_get_config_dir(self):
- """
- nice config dir? (win)
- """
- self._missing_test_for_plat(do_raise=True)
-
- # provider paths
-
- @unittest.skipUnless(_system == "Linux", "linux only")
- def test_get_default_provider_path(self):
- """
- is default provider path ok?
- """
- self.assertEqual(
- config.get_default_provider_path(),
- os.path.expanduser(
- '~/.config/leap/providers/%s/' %
- constants.DEFAULT_PROVIDER)
- )
-
- # validate ip
-
- def test_validate_ip(self):
- """
- check our ip validation
- """
- config.validate_ip('3.3.3.3')
- with self.assertRaises(socket.error):
- config.validate_ip('255.255.255.256')
- with self.assertRaises(socket.error):
- config.validate_ip('foobar')
-
- @unittest.skip
- def test_validate_domain(self):
- """
- code to be written yet
- """
- raise NotImplementedError
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/base/tests/test_providers.py b/src/leap/base/tests/test_providers.py
deleted file mode 100644
index f257f54d..00000000
--- a/src/leap/base/tests/test_providers.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import copy
-import json
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-import os
-
-import jsonschema
-
-#from leap import __branding as BRANDING
-from leap.testing.basetest import BaseLeapTest
-from leap.base import providers
-
-
-EXPECTED_DEFAULT_CONFIG = {
- u"api_version": u"0.1.0",
- #u"description": "LEAPTranslatable<{u'en': u'Test provider'}>",
- u"description": {u'en': u'Test provider'},
- u"default_language": u"en",
- #u"display_name": {u'en': u"Test Provider"},
- u"domain": u"testprovider.example.org",
- #u'name': "LEAPTranslatable<{u'en': u'Test Provider'}>",
- u'name': {u'en': u'Test Provider'},
- u"enrollment_policy": u"open",
- #u"serial": 1,
- u"services": [
- u"eip"
- ],
- u"languages": [u"en"],
- u"version": u"0.1.0"
-}
-
-
-class TestLeapProviderDefinition(BaseLeapTest):
- def setUp(self):
- self.domain = "testprovider.example.org"
- self.definition = providers.LeapProviderDefinition(
- domain=self.domain)
- self.definition.save(force=True)
- self.definition.load() # why have to load after save??
- self.config = self.definition.config
-
- def tearDown(self):
- if hasattr(self, 'testfile') and os.path.isfile(self.testfile):
- os.remove(self.testfile)
-
- # tests
-
- # XXX most of these tests can be made more abstract
- # and moved to test_baseconfig *triangulate!*
-
- def test_provider_slug_property(self):
- slug = self.definition.slug
- self.assertEquals(
- slug,
- os.path.join(
- self.home,
- '.config', 'leap', 'providers',
- '%s' % self.domain,
- 'provider.json'))
- with self.assertRaises(AttributeError):
- self.definition.slug = 23
-
- def test_provider_dump(self):
- # check a good provider definition is dumped to disk
- self.testfile = self.get_tempfile('test.json')
- self.definition.save(to=self.testfile, force=True)
- deserialized = json.load(open(self.testfile, 'rb'))
- self.maxDiff = None
- #import ipdb;ipdb.set_trace()
- self.assertEqual(deserialized, EXPECTED_DEFAULT_CONFIG)
-
- def test_provider_dump_to_slug(self):
- # same as above, but we test the ability to save to a
- # file generated from the slug.
- # XXX THIS TEST SHOULD MOVE TO test_baseconfig
- self.definition.save()
- filename = self.definition.filename
- self.assertTrue(os.path.isfile(filename))
- deserialized = json.load(open(filename, 'rb'))
- self.assertEqual(deserialized, EXPECTED_DEFAULT_CONFIG)
-
- def test_provider_load(self):
- # check loading provider from disk file
- self.testfile = self.get_tempfile('test_load.json')
- with open(self.testfile, 'w') as wf:
- wf.write(json.dumps(EXPECTED_DEFAULT_CONFIG))
- self.definition.load(fromfile=self.testfile)
- #self.assertDictEqual(self.config,
- #EXPECTED_DEFAULT_CONFIG)
- self.assertItemsEqual(self.config, EXPECTED_DEFAULT_CONFIG)
-
- def test_provider_validation(self):
- self.definition.validate(self.config)
- _config = copy.deepcopy(self.config)
- # bad type, raise validation error
- _config['domain'] = 111
- with self.assertRaises(jsonschema.ValidationError):
- self.definition.validate(_config)
-
- @unittest.skip
- def test_load_malformed_json_definition(self):
- raise NotImplementedError
-
- @unittest.skip
- def test_type_validation(self):
- # check various type validation
- # type cast
- raise NotImplementedError
-
-
-class TestLeapProviderSet(BaseLeapTest):
-
- def setUp(self):
- self.providers = providers.LeapProviderSet()
-
- def tearDown(self):
- pass
- ###
-
- def test_get_zero_count(self):
- self.assertEqual(self.providers.count, 0)
-
- @unittest.skip
- def test_count_defined_providers(self):
- # check the method used for making
- # the list of providers
- raise NotImplementedError
-
- @unittest.skip
- def test_get_default_provider(self):
- raise NotImplementedError
-
- @unittest.skip
- def test_should_be_at_least_one_provider_after_init(self):
- # when we init an empty environment,
- # there should be at least one provider,
- # that will be a dump of the default provider definition
- # somehow a high level test
- raise NotImplementedError
-
- @unittest.skip
- def test_get_eip_remote_from_default_provider(self):
- # from: default provider
- # expect: remote eip domain
- raise NotImplementedError
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/base/tests/test_validation.py b/src/leap/base/tests/test_validation.py
deleted file mode 100644
index 87e99648..00000000
--- a/src/leap/base/tests/test_validation.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import copy
-import datetime
-#import json
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-import os
-
-import jsonschema
-
-from leap.base.config import JSONLeapConfig
-from leap.base import pluggableconfig
-from leap.testing.basetest import BaseLeapTest
-
-SAMPLE_CONFIG_DICT = {
- 'prop_one': 1,
- 'prop_uri': "http://example.org",
- 'prop_date': '2012-12-12',
-}
-
-EXPECTED_CONFIG = {
- 'prop_one': 1,
- 'prop_uri': "http://example.org",
- 'prop_date': datetime.datetime(2012, 12, 12)
-}
-
-sample_spec = {
- 'description': 'sample schema definition',
- 'type': 'object',
- 'properties': {
- 'prop_one': {
- 'type': int,
- 'default': 1,
- 'required': True
- },
- 'prop_uri': {
- 'type': str,
- 'default': 'http://example.org',
- 'required': True,
- 'format': 'uri'
- },
- 'prop_date': {
- 'type': str,
- 'default': '2012-12-12',
- 'format': 'date'
- }
- }
-}
-
-
-class SampleConfig(JSONLeapConfig):
- spec = sample_spec
-
- @property
- def slug(self):
- return os.path.expanduser('~/sampleconfig.json')
-
-
-class TestJSONLeapConfigValidation(BaseLeapTest):
- def setUp(self):
- self.sampleconfig = SampleConfig()
- self.sampleconfig.save()
- self.sampleconfig.load()
- self.config = self.sampleconfig.config
-
- def tearDown(self):
- if hasattr(self, 'testfile') and os.path.isfile(self.testfile):
- os.remove(self.testfile)
-
- # tests
-
- def test_good_validation(self):
- self.sampleconfig.validate(SAMPLE_CONFIG_DICT)
-
- def test_broken_int(self):
- _config = copy.deepcopy(SAMPLE_CONFIG_DICT)
- _config['prop_one'] = '1'
- with self.assertRaises(jsonschema.ValidationError):
- self.sampleconfig.validate(_config)
-
- def test_format_property(self):
- # JsonSchema Validator does not check the format property.
- # We should have to extend the Configuration class
- blah = copy.deepcopy(SAMPLE_CONFIG_DICT)
- blah['prop_uri'] = 'xxx'
- with self.assertRaises(pluggableconfig.TypeCastException):
- self.sampleconfig.validate(blah)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/baseapp/constants.py b/src/leap/baseapp/constants.py
deleted file mode 100644
index e312be21..00000000
--- a/src/leap/baseapp/constants.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# This timer used for polling vpn manager state.
-
-# XXX what is an optimum polling interval?
-# too little will be overkill, too much will
-# miss transition states.
-TIMER_MILLISECONDS = 250.0
diff --git a/src/leap/baseapp/dialogs.py b/src/leap/baseapp/dialogs.py
index d256fc99..4b1b5b62 100644
--- a/src/leap/baseapp/dialogs.py
+++ b/src/leap/baseapp/dialogs.py
@@ -1,61 +1,33 @@
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
-import logging
-
from PyQt4.QtGui import (QDialog, QFrame, QPushButton, QLabel, QMessageBox)
-logger = logging.getLogger(name=__name__)
-
class ErrorDialog(QDialog):
- def __init__(self, parent=None, errtype=None, msg=None, label=None):
+ def __init__(self, parent=None):
super(ErrorDialog, self).__init__(parent)
+
frameStyle = QFrame.Sunken | QFrame.Panel
self.warningLabel = QLabel()
self.warningLabel.setFrameStyle(frameStyle)
self.warningButton = QPushButton("QMessageBox.&warning()")
- if msg is not None:
- self.msg = msg
- if label is not None:
- self.label = label
- if errtype == "critical":
- self.criticalMessage(self.msg, self.label)
-
def warningMessage(self, msg, label):
msgBox = QMessageBox(QMessageBox.Warning,
- "LEAP Client Error",
- msg,
+ "QMessageBox.warning()", msg,
QMessageBox.NoButton, self)
msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ msgBox.addButton("&Cancel", QMessageBox.RejectRole)
if msgBox.exec_() == QMessageBox.AcceptRole:
- pass
- # do whatever we want to do after
- # closing the dialog. we can pass that
- # in the constructor
+ self.warningLabel.setText("Save Again")
+ else:
+ self.warningLabel.setText("Continue")
def criticalMessage(self, msg, label):
msgBox = QMessageBox(QMessageBox.Critical,
- "LEAP Client Error",
- msg,
- QMessageBox.NoButton, self)
- msgBox.addButton("&Ok", QMessageBox.AcceptRole)
- msgBox.exec_()
-
- # It's critical, so we exit.
- # We should better emit a signal and connect it
- # with the proper shutdownAndQuit method, but
- # this suffices for now.
- logger.info('Quitting')
- import sys
- sys.exit()
-
- def confirmMessage(self, msg, label, action):
- msgBox = QMessageBox(QMessageBox.Critical,
- self.tr("LEAP Client Error"),
- msg,
+ "QMessageBox.critical()", msg,
QMessageBox.NoButton, self)
msgBox.addButton("&Ok", QMessageBox.AcceptRole)
msgBox.addButton("&Cancel", QMessageBox.RejectRole)
-
if msgBox.exec_() == QMessageBox.AcceptRole:
- action()
+ self.warningLabel.setText("Save Again")
+ else:
+ self.warningLabel.setText("Continue")
diff --git a/src/leap/baseapp/eip.py b/src/leap/baseapp/eip.py
deleted file mode 100644
index b34cc82e..00000000
--- a/src/leap/baseapp/eip.py
+++ /dev/null
@@ -1,243 +0,0 @@
-from __future__ import print_function
-import logging
-import time
-#import sys
-
-from PyQt4 import QtCore
-
-from leap.baseapp.dialogs import ErrorDialog
-from leap.baseapp import constants
-from leap.eip import exceptions as eip_exceptions
-from leap.eip.eipconnection import EIPConnection
-from leap.base.checks import EVENT_CONNECT_REFUSED
-from leap.util import geo
-
-logger = logging.getLogger(name=__name__)
-
-
-class EIPConductorAppMixin(object):
- """
- initializes an instance of EIPConnection,
- gathers errors, and passes status-change signals
- from Qt land along to the conductor.
- Connects the eip connect/disconnect logic
- to the switches in the app (buttons/menu items).
- """
- ERR_DIALOG = False
-
- def __init__(self, *args, **kwargs):
- opts = kwargs.pop('opts')
- config_file = getattr(opts, 'config_file', None)
- provider = kwargs.pop('provider')
-
- self.eip_service_started = False
-
- # conductor (eip connection) is in charge of all
- # vpn-related configuration / monitoring.
- # we pass a tuple of signals that will be
- # triggered when status changes.
-
- self.conductor = EIPConnection(
- watcher_cb=self.newLogLine.emit,
- config_file=config_file,
- checker_signals=(self.eipStatusChange.emit, ),
- status_signals=(self.openvpnStatusChange.emit, ),
- debug=self.debugmode,
- ovpn_verbosity=opts.openvpn_verb,
- provider=provider)
-
- # Do we want to enable the skip checks w/o being
- # in debug mode??
- #self.skip_download = opts.no_provider_checks
- #self.skip_verify = opts.no_ca_verify
- self.skip_download = False
- self.skip_verify = False
-
- def run_eip_checks(self):
- """
- runs eip checks and
- the error checking loop
- """
- logger.debug('running EIP CHECKS')
- self.conductor.run_checks(
- skip_download=self.skip_download,
- skip_verify=self.skip_verify)
- self.error_check()
-
- self.start_eipconnection.emit()
-
- def error_check(self):
- """
- consumes the conductor error queue.
- pops errors, and acts accordingly (launching user dialogs).
- """
- logger.debug('error check')
-
- errq = self.conductor.error_queue
- while errq.qsize() != 0:
- logger.debug('%s errors left in conductor queue', errq.qsize())
- # we get exception and original traceback from queue
- error, tb = errq.get()
-
- # redundant log, debugging the loop.
- logger.error('%s: %s', error.__class__.__name__, error.message)
-
- if issubclass(error.__class__, eip_exceptions.EIPClientError):
- self.triggerEIPError.emit(error)
-
- else:
- # deprecated form of raising exception.
- raise error, None, tb
-
- if error.failfirst is True:
- break
-
- @QtCore.pyqtSlot(object)
- def onEIPError(self, error):
- """
- check severity and launches
- dialogs informing user about the errors.
- in the future we plan to derive errors to
- our log viewer.
- """
- if self.ERR_DIALOG:
- logger.warning('another error dialog suppressed')
- return
-
- # XXX this is actually a one-shot.
- # On the dialog there should be
- # a reset signal binded to the ok button
- # or something like that.
- self.ERR_DIALOG = True
-
- if getattr(error, 'usermessage', None):
- message = error.usermessage
- else:
- message = error.message
-
- # XXX
- # check headless = False before
- # launching dialog.
- # (so Qt tests can assert stuff)
-
- if error.critical:
- logger.critical(error.message)
- #critical error (non recoverable),
- #we give user some info and quit.
- #(critical error dialog will exit app)
- ErrorDialog(errtype="critical",
- msg=message,
- label="critical error")
-
- elif error.warning:
- logger.warning(error.message)
-
- else:
- dialog = ErrorDialog()
- dialog.warningMessage(message, 'error')
-
- @QtCore.pyqtSlot()
- def statusUpdate(self):
- """
- polls status and updates ui with real time
- info about transferred bytes / connection state.
- right now is triggered by a timer tick
- (timer controlled by StatusAwareTrayIcon class)
- """
- # TODO I guess it's too expensive to poll
- # continously. move to signal events instead.
- # (i.e., subscribe to connection status changes
- # from openvpn manager)
-
- if not self.eip_service_started:
- # there is a race condition
- # going on here. Depending on how long we take
- # to init the qt app, the management socket
- # is not ready yet.
- return
-
- #if self.conductor.with_errors:
- #XXX how to wait on pkexec???
- #something better that this workaround, plz!!
- #I removed the pkexec pass authentication at all.
- #time.sleep(5)
- #logger.debug('timeout')
- #logger.error('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.setIconToolTip()
-
- 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)
- self.remote_country.setText(
- geo.get_country_name(remote))
-
- # status i/o
-
- status = self.conductor.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)
-
- # connection information via management interface
- log = self.conductor.get_log()
- error_matrix = [(EVENT_CONNECT_REFUSED, (self.start_or_stopVPN, ))]
- if hasattr(self.network_checker, 'checker'):
- self.network_checker.checker.parse_log_and_react(log, error_matrix)
-
- @QtCore.pyqtSlot()
- def start_or_stopVPN(self, **kwargs):
- """
- stub for running child process with vpn
- """
- if self.conductor.has_errors():
- logger.debug('not starting vpn; conductor has errors')
- return
-
- if self.eip_service_started is False:
- try:
- self.conductor.connect()
-
- except eip_exceptions.EIPNoCommandError as exc:
- logger.error('tried to run openvpn but no command is set')
- self.triggerEIPError.emit(exc)
-
- except Exception as err:
- # raise generic exception (Bad Thing Happened?)
- logger.exception(err)
- else:
- # no errors, so go on.
- if self.debugmode:
- self.startStopButton.setText(self.tr('&Disconnect'))
- self.eip_service_started = True
- self.toggleEIPAct()
-
- # XXX decouple! (timer is init by icons class).
- # we could bring Timer Init to this Mixin
- # or to its own Mixin.
- self.timer.start(constants.TIMER_MILLISECONDS)
- return
-
- if self.eip_service_started is True:
- self.network_checker.stop()
- self.conductor.disconnect()
- if self.debugmode:
- self.startStopButton.setText(self.tr('&Connect'))
- self.eip_service_started = False
- self.toggleEIPAct()
- self.timer.stop()
- return
diff --git a/src/leap/baseapp/leap_app.py b/src/leap/baseapp/leap_app.py
deleted file mode 100644
index 4d3aebd6..00000000
--- a/src/leap/baseapp/leap_app.py
+++ /dev/null
@@ -1,153 +0,0 @@
-import logging
-
-import sip
-sip.setapi('QVariant', 2)
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap.gui import mainwindow_rc
-
-logger = logging.getLogger(name=__name__)
-
-
-APP_LOGO = ':/images/leap-color-small.png'
-
-
-class MainWindowMixin(object):
- """
- create the main window
- for leap app
- """
-
- def __init__(self, *args, **kwargs):
- # XXX set initial visibility
- # debug = no visible
-
- widget = QtGui.QWidget()
- self.setCentralWidget(widget)
-
- mainLayout = QtGui.QVBoxLayout()
- # add widgets to layout
- #self.createWindowHeader()
- #mainLayout.addWidget(self.headerBox)
-
- # created in systray
- mainLayout.addWidget(self.statusIconBox)
- if self.debugmode:
- mainLayout.addWidget(self.statusBox)
- mainLayout.addWidget(self.loggerBox)
- widget.setLayout(mainLayout)
-
- self.createMainActions()
- self.createMainMenus()
-
- self.setWindowTitle("LEAP Client")
- self.set_app_icon()
- self.set_statusbarMessage('ready')
-
- def createMainActions(self):
- #self.openAct = QtGui.QAction("&Open...", self, shortcut="Ctrl+O",
- #triggered=self.open)
-
- self.firstRunWizardAct = QtGui.QAction(
- "&First run wizard...", self,
- triggered=self.stop_connection_and_launch_first_run_wizard)
- self.aboutAct = QtGui.QAction("&About", self, triggered=self.about)
-
- #self.aboutQtAct = QtGui.QAction("About &Qt", self,
- #triggered=QtGui.qApp.aboutQt)
-
- def createMainMenus(self):
- self.connMenu = QtGui.QMenu("&Connections", self)
- #self.viewMenu.addSeparator()
- self.connMenu.addAction(self.quitAction)
-
- self.settingsMenu = QtGui.QMenu("&Settings", self)
- self.settingsMenu.addAction(self.firstRunWizardAct)
-
- self.helpMenu = QtGui.QMenu("&Help", self)
- self.helpMenu.addAction(self.aboutAct)
- #self.helpMenu.addAction(self.aboutQtAct)
-
- self.menuBar().addMenu(self.connMenu)
- self.menuBar().addMenu(self.settingsMenu)
- self.menuBar().addMenu(self.helpMenu)
-
- def stop_connection_and_launch_first_run_wizard(self):
- settings = QtCore.QSettings()
- settings.setValue('FirstRunWizardDone', False)
- logger.debug('should run first run wizard again...')
-
- status = self.conductor.get_icon_name()
- if status != "disconnected":
- self.start_or_stopVPN()
-
- self.launch_first_run_wizard()
- #from leap.gui.firstrunwizard import FirstRunWizard
- #wizard = FirstRunWizard(
- #parent=self,
- #success_cb=self.initReady.emit)
- #wizard.show()
-
- def set_app_icon(self):
- icon = QtGui.QIcon(APP_LOGO)
- self.setWindowIcon(icon)
-
- #def createWindowHeader(self):
- #"""
- #description lines for main window
- #"""
- #self.headerBox = QtGui.QGroupBox()
- #self.headerLabel = QtGui.QLabel(
- #"<font size=40>LEAP Encryption Access Project</font>")
- #self.headerLabelSub = QtGui.QLabel(
- #"<br><i>your internet encryption toolkit</i>")
-#
- #pixmap = QtGui.QPixmap(APP_LOGO)
- #leap_lbl = QtGui.QLabel()
- #leap_lbl.setPixmap(pixmap)
-#
- #headerLayout = QtGui.QHBoxLayout()
- #headerLayout.addWidget(leap_lbl)
- #headerLayout.addWidget(self.headerLabel)
- #headerLayout.addWidget(self.headerLabelSub)
- #headerLayout.addStretch()
- #self.headerBox.setLayout(headerLayout)
-
- def set_statusbarMessage(self, msg):
- self.statusBar().showMessage(msg)
-
- def closeEvent(self, event):
- """
- redefines close event (persistent window behaviour)
- """
- if self.trayIcon.isVisible() and not self.debugmode:
- QtGui.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()
- return
- self.cleanupAndQuit()
-
- def cleanupAndQuit(self):
- """
- cleans state before shutting down app.
- """
- # save geometry for restoring
- settings = QtCore.QSettings()
- geom_key = "DebugGeometry" if self.debugmode else "Geometry"
- settings.setValue(geom_key, self.saveGeometry())
-
- # TODO:make sure to shutdown all child process / threads
- # in conductor
- # XXX send signal instead?
- logger.info('Shutting down')
- self.conductor.disconnect(shutdown=True)
- logger.info('Exiting. Bye.')
- QtGui.qApp.quit()
diff --git a/src/leap/baseapp/log.py b/src/leap/baseapp/log.py
deleted file mode 100644
index 636e5bae..00000000
--- a/src/leap/baseapp/log.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import logging
-
-from PyQt4 import QtGui
-from PyQt4 import QtCore
-
-vpnlogger = logging.getLogger('leap.openvpn')
-
-
-class LogPaneMixin(object):
- """
- a simple log pane
- that writes new lines as they come
- """
- EXCLUDES = ('MANAGEMENT',)
-
- def createLogBrowser(self):
- """
- creates Browser widget for displaying logs
- (in debug mode only).
- """
- self.loggerBox = QtGui.QGroupBox()
- logging_layout = QtGui.QVBoxLayout()
- self.logbrowser = QtGui.QTextBrowser()
-
- startStopButton = QtGui.QPushButton(self.tr("&Connect"))
- self.startStopButton = startStopButton
-
- logging_layout.addWidget(self.logbrowser)
- logging_layout.addWidget(self.startStopButton)
- self.loggerBox.setLayout(logging_layout)
-
- # status box
-
- self.statusBox = QtGui.QGroupBox()
- grid = QtGui.QGridLayout()
-
- self.updateTS = QtGui.QLabel('')
- self.status_label = QtGui.QLabel(self.tr('Disconnected'))
- self.ip_label = QtGui.QLabel('')
- self.remote_label = QtGui.QLabel('')
- self.remote_country = QtGui.QLabel('')
-
- tun_read_label = QtGui.QLabel("tun read")
- self.tun_read_bytes = QtGui.QLabel("0")
- tun_write_label = QtGui.QLabel("tun write")
- self.tun_write_bytes = QtGui.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(self.remote_country, 2, 1)
- grid.addWidget(tun_read_label, 3, 0)
- grid.addWidget(self.tun_read_bytes, 3, 1)
- grid.addWidget(tun_write_label, 4, 0)
- grid.addWidget(self.tun_write_bytes, 4, 1)
-
- self.statusBox.setLayout(grid)
-
- @QtCore.pyqtSlot(str)
- def onLoggerNewLine(self, line):
- """
- simple slot: writes new line to logger Pane.
- """
- msg = line[:-1]
- if self.debugmode and all(map(lambda w: w not in msg,
- LogPaneMixin.EXCLUDES)):
- self.logbrowser.append(msg)
- vpnlogger.info(msg)
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 91b0dc61..85129a9b 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -1,191 +1,467 @@
# vim: set fileencoding=utf-8 :
#!/usr/bin/env python
import logging
+import time
+logger = logging.getLogger(name=__name__)
-import sip
-sip.setapi('QString', 2)
-sip.setapi('QVariant', 2)
+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 PyQt4 import QtCore
-from PyQt4 import QtGui
+from leap.baseapp.dialogs import ErrorDialog
+from leap.eip.conductor import (EIPConductor,
+ EIPNoCommandError)
-from leap.baseapp.eip import EIPConductorAppMixin
-from leap.baseapp.log import LogPaneMixin
-from leap.baseapp.systray import StatusAwareTrayIconMixin
-from leap.baseapp.network import NetworkCheckerAppMixin
-from leap.baseapp.leap_app import MainWindowMixin
-from leap.eip.checks import ProviderCertChecker
-from leap.gui.threads import FunThread
+from leap.eip.config import (EIPInitBadKeyFilePermError)
+# from leap.eip import exceptions as eip_exceptions
+
+from leap.gui import mainwindow_rc
-logger = logging.getLogger(name=__name__)
+class LeapWindow(QMainWindow):
+ #XXX tbd: refactor into model / view / controller
+ #and put in its own modules...
-class LeapWindow(QtGui.QMainWindow,
- MainWindowMixin, EIPConductorAppMixin,
- StatusAwareTrayIconMixin,
- NetworkCheckerAppMixin,
- LogPaneMixin):
- """
- main window for the leap app.
- Initializes all of its base classes
- We keep here some signal initialization
- that gets tricky otherwise.
- """
-
- # signals
-
- newLogLine = QtCore.pyqtSignal([str])
- mainappReady = QtCore.pyqtSignal([])
- initReady = QtCore.pyqtSignal([])
- networkError = QtCore.pyqtSignal([object])
- triggerEIPError = QtCore.pyqtSignal([object])
- start_eipconnection = QtCore.pyqtSignal([])
- shutdownSignal = QtCore.pyqtSignal([])
- initNetworkChecker = QtCore.pyqtSignal([])
-
- # this is status change got from openvpn management
- openvpnStatusChange = QtCore.pyqtSignal([object])
- # this is global eip status
- eipStatusChange = QtCore.pyqtSignal([str])
+ newLogLine = pyqtSignal([str])
+ statusChange = pyqtSignal([object])
def __init__(self, opts):
- logger.debug('init leap window')
- self.debugmode = getattr(opts, 'debug', False)
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()
- settings = QtCore.QSettings()
- self.provider_domain = settings.value("provider_domain", None)
- self.username = settings.value("username", None)
+ # create timer
+ self.timer = QTimer()
- logger.debug('provider: %s', self.provider_domain)
- logger.debug('username: %s', self.username)
+ # bind signals
- provider = self.provider_domain
- EIPConductorAppMixin.__init__(
- self, opts=opts, provider=provider)
- StatusAwareTrayIconMixin.__init__(self)
+ self.trayIcon.activated.connect(self.iconActivated)
+ self.newLogLine.connect(self.onLoggerNewLine)
+ self.statusChange.connect(self.onStatusChange)
+ self.timer.timeout.connect(self.onTimerTick)
- # XXX network checker should probably not
- # trigger run_checks on init... but wait
- # for ready signal instead...
- NetworkCheckerAppMixin.__init__(self, provider=provider)
- MainWindowMixin.__init__(self)
+ widget = QWidget()
+ self.setCentralWidget(widget)
- geom_key = "DebugGeometry" if self.debugmode else "Geometry"
- geom = settings.value(geom_key)
- if geom:
- self.restoreGeometry(geom)
+ # 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)
- # XXX check for wizard
- self.wizard_done = settings.value("FirstRunWizardDone")
+ self.trayIcon.show()
+ config_file = getattr(opts, 'config_file', None)
- self.initchecks = FunThread(self.run_eip_checks)
+ #
+ # conductor is in charge of all
+ # vpn-related configuration / monitoring.
+ # we pass a tuple of signals that will be
+ # triggered when status changes.
+ #
+ self.conductor = EIPConductor(
+ watcher_cb=self.newLogLine.emit,
+ config_file=config_file,
+ status_signals=(self.statusChange.emit, ),
+ debug=self.debugmode)
- # bind signals
- self.initchecks.finished.connect(
- lambda: logger.debug('Initial checks thread finished'))
- self.trayIcon.activated.connect(self.iconActivated)
- self.newLogLine.connect(
- lambda line: self.onLoggerNewLine(line))
- self.timer.timeout.connect(
- lambda: self.onTimerTick())
- self.networkError.connect(
- lambda exc: self.onNetworkError(exc))
- self.triggerEIPError.connect(
- lambda exc: self.onEIPError(exc))
+ #
+ # bunch of self checks.
+ # XXX move somewhere else alltogether.
+ #
+
+ if self.conductor.missing_provider is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Missing provider. Add a remote_ip entry '
+ 'under section [provider] in eip.cfg',
+ 'error')
+
+ if self.conductor.missing_vpn_keyfile is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Could not find the vpn keys file',
+ 'error')
+
+ # ... btw, review pending.
+ # os.kill of subprocess fails if we have
+ # some of this errors.
+
+ if self.conductor.bad_provider is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Bad provider entry. Check that remote_ip entry '
+ 'has an IP under section [provider] in eip.cfg',
+ 'error')
+
+ if self.conductor.bad_keyfile_perms is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'The vpn keys file has bad permissions',
+ 'error')
+
+ if self.conductor.missing_auth_agent is True:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'We could not find any authentication '
+ 'agent in your system.<br/>'
+ 'Make sure you have '
+ '<b>polkit-gnome-authentication-agent-1</b> '
+ 'running and try again.',
+ 'error')
+
+ if self.conductor.missing_pkexec is True:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'We could not find <b>pkexec</b> in your '
+ 'system.<br/> Do you want to try '
+ '<b>setuid workaround</b>? '
+ '(<i>DOES NOTHING YET</i>)',
+ 'error')
+
+ self.setWindowTitle("LEAP Client")
+ 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.startStopButton.clicked.connect(
- lambda: self.start_or_stopVPN())
- self.start_eipconnection.connect(
- self.do_start_eipconnection)
- self.shutdownSignal.connect(
- self.cleanupAndQuit)
- self.initNetworkChecker.connect(
- lambda: self.init_network_checker(self.conductor.provider))
-
- # status change.
- # TODO unify
- self.openvpnStatusChange.connect(
- lambda status: self.onOpenVPNStatusChange(status))
- self.eipStatusChange.connect(
- lambda newstatus: self.onEIPConnStatusChange(newstatus))
- self.eipStatusChange.connect(
- lambda newstatus: self.toggleEIPAct())
-
- # do first run wizard and init signals
- self.mainappReady.connect(self.do_first_run_wizard_check)
- self.initReady.connect(self.runchecks_and_eipconnect)
-
- # ... all ready. go!
- # connected to do_first_run_wizard_check
- self.mainappReady.emit()
-
- def do_first_run_wizard_check(self):
- """
- checks whether first run wizard needs to be run
- launches it if needed
- and emits initReady signal if not.
- """
-
- logger.debug('first run wizard check...')
- need_wizard = False
-
- # do checks (can overlap if wizard was interrupted)
- if not self.wizard_done:
- need_wizard = True
-
- if not self.provider_domain:
- need_wizard = True
- else:
- pcertchecker = ProviderCertChecker(domain=self.provider_domain)
- if not pcertchecker.is_cert_valid(do_raise=False):
- logger.warning('missing valid client cert. need wizard')
- need_wizard = True
-
- # launch wizard if needed
- if need_wizard:
- logger.debug('running first run wizard')
- self.launch_first_run_wizard()
- else: # no wizard needed
- self.initReady.emit()
-
- def launch_first_run_wizard(self):
- """
- launches wizard and blocks
- """
- from leap.gui.firstrun.wizard import FirstRunWizard
- wizard = FirstRunWizard(
- self.conductor,
- parent=self,
- username=self.username,
- start_eipconnection_signal=self.start_eipconnection,
- eip_statuschange_signal=self.eipStatusChange,
- quitcallback=self.onWizardCancel)
- wizard.show()
-
- def onWizardCancel(self):
- if not self.wizard_done:
- logger.debug(
- 'clicked on Cancel during first '
- 'run wizard. shutting down')
self.cleanupAndQuit()
- def runchecks_and_eipconnect(self):
+ 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)
"""
- shows icon and run init checks
+ 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):
"""
- self.show_systray_icon()
- self.initchecks.begin()
+ 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.setIcon('disconnected')
+ 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:
+ try:
+ self.conductor.connect()
+ except EIPNoCommandError:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'No suitable openvpn command found. '
+ '<br/>(Might be a permissions problem)',
+ 'error')
+ 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 do_start_eipconnection(self):
+ def cleanupAndQuit(self):
"""
- shows icon and init eip connection
- called from the end of wizard
+ cleans state before shutting down app.
"""
- self.show_systray_icon()
- # this will setup the command
- self.conductor.run_openvpn_checks()
- self.start_or_stopVPN()
+ # TODO:make sure to shutdown all child process / threads
+ # in conductor
+ self.conductor.cleanup()
+ qApp.quit()
diff --git a/src/leap/baseapp/network.py b/src/leap/baseapp/network.py
deleted file mode 100644
index dc5182a4..00000000
--- a/src/leap/baseapp/network.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from __future__ import print_function
-
-import logging
-
-logger = logging.getLogger(name=__name__)
-
-from PyQt4 import QtCore
-
-from leap.baseapp.dialogs import ErrorDialog
-from leap.base.network import NetworkCheckerThread
-
-from leap.util.misc import null_check
-
-
-class NetworkCheckerAppMixin(object):
- """
- initialize an instance of the Network Checker,
- which gathers error and passes them on.
- """
- ERR_NETERR = False
-
- def __init__(self, *args, **kwargs):
- provider = kwargs.pop('provider', None)
- self.network_checker = None
- if provider:
- self.init_network_checker(provider)
-
- def init_network_checker(self, provider):
- null_check(provider, "provider_domain")
- if not self.network_checker:
- self.network_checker = NetworkCheckerThread(
- error_cb=self.networkError.emit,
- debug=self.debugmode,
- provider=provider)
- self.network_checker.start()
-
- @QtCore.pyqtSlot(object)
- def runNetworkChecks(self):
- logger.debug('running checks (from NetworkChecker Mixin slot)')
- self.network_checker.run_checks()
-
- @QtCore.pyqtSlot(object)
- def onNetworkError(self, exc):
- """
- slot that receives a network exceptions
- and raises a user error message
- """
- # FIXME this should not HANDLE anything after
- # the network check thread has been stopped.
-
- logger.debug('handling network exception')
- if not self.ERR_NETERR:
- self.ERR_NETERR = True
-
- logger.error(exc.message)
- dialog = ErrorDialog(parent=self)
- if exc.critical:
- dialog.criticalMessage(exc.usermessage, "network error")
- else:
- dialog.warningMessage(exc.usermessage, "network error")
-
- self.start_or_stopVPN()
- self.network_checker.stop()
diff --git a/src/leap/baseapp/systray.py b/src/leap/baseapp/systray.py
deleted file mode 100644
index 77eb3fe9..00000000
--- a/src/leap/baseapp/systray.py
+++ /dev/null
@@ -1,268 +0,0 @@
-import logging
-import sys
-
-import sip
-sip.setapi('QString', 2)
-sip.setapi('QVariant', 2)
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap import __branding as BRANDING
-from leap import __version__ as VERSION
-
-from leap.gui import mainwindow_rc
-
-logger = logging.getLogger(__name__)
-
-
-class StatusAwareTrayIconMixin(object):
- """
- a mix of several functions needed
- to create a systray and make it
- get updated from conductor status
- polling.
- """
- states = {
- "disconnected": 0,
- "connecting": 1,
- "connected": 2}
-
- iconpath = {
- "disconnected": ':/images/conn_error.png',
- "connecting": ':/images/conn_connecting.png',
- "connected": ':/images/conn_connected.png'}
-
- Icons = {
- 'disconnected': lambda self: QtGui.QIcon(
- self.iconpath['disconnected']),
- 'connecting': lambda self: QtGui.QIcon(
- self.iconpath['connecting']),
- 'connected': lambda self: QtGui.QIcon(
- self.iconpath['connected'])
- }
-
- def __init__(self, *args, **kwargs):
- self.createIconGroupBox()
- self.createActions()
- self.createTrayIcon()
-
- # not sure if this really belongs here, but...
- self.timer = QtCore.QTimer()
-
- def show_systray_icon(self):
- #logger.debug('showing tray icon................')
- self.trayIcon.show()
-
- def createIconGroupBox(self):
- """
- dummy icongroupbox
- (to be removed from here -- reference only)
- """
- con_widgets = {
- 'disconnected': QtGui.QLabel(),
- 'connecting': QtGui.QLabel(),
- 'connected': QtGui.QLabel(),
- }
- con_widgets['disconnected'].setPixmap(
- QtGui.QPixmap(
- self.iconpath['disconnected']))
- con_widgets['connecting'].setPixmap(
- QtGui.QPixmap(
- self.iconpath['connecting']))
- con_widgets['connected'].setPixmap(
- QtGui.QPixmap(
- self.iconpath['connected'])),
- self.ConnectionWidgets = con_widgets
-
- self.statusIconBox = QtGui.QGroupBox(
- self.tr("EIP Connection Status"))
- statusIconLayout = QtGui.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.leapConnStatus = QtGui.QLabel(
- self.tr("<b>disconnected</b>"))
- statusIconLayout.addWidget(self.leapConnStatus)
-
- self.statusIconBox.setLayout(statusIconLayout)
-
- def createTrayIcon(self):
- """
- creates the tray icon
- """
- self.trayIconMenu = QtGui.QMenu(self)
-
- self.trayIconMenu.addAction(self.connAct)
- self.trayIconMenu.addSeparator()
- self.trayIconMenu.addAction(self.detailsAct)
- self.trayIconMenu.addSeparator()
- self.trayIconMenu.addAction(self.aboutAct)
- # we should get this hidden inside the "about" dialog
- # (as a little button maybe)
- #self.trayIconMenu.addAction(self.aboutQtAct)
- self.trayIconMenu.addSeparator()
- self.trayIconMenu.addAction(self.quitAction)
-
- self.trayIcon = QtGui.QSystemTrayIcon(self)
- self.setIcon('disconnected')
- self.trayIcon.setContextMenu(self.trayIconMenu)
-
- #self.trayIconMenu.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- #self.trayIconMenu.customContextMenuRequested.connect(
- #self.on_context_menu)
-
- #def bad(self):
- #logger.error('this should not be called')
-
- def createActions(self):
- """
- creates actions to be binded to tray icon
- """
- # XXX change action name on (dis)connect
- self.connAct = QtGui.QAction(
- self.tr("Encryption ON turn &off"),
- self,
- triggered=lambda: self.start_or_stopVPN())
-
- self.detailsAct = QtGui.QAction(
- self.tr("&Details..."),
- self,
- triggered=self.detailsWin)
- self.aboutAct = QtGui.QAction(
- self.tr("&About"), self,
- triggered=self.about)
- self.aboutQtAct = QtGui.QAction(
- self.tr("About Q&t"), self,
- triggered=QtGui.qApp.aboutQt)
- self.quitAction = QtGui.QAction(
- self.tr("&Quit"), self,
- triggered=self.cleanupAndQuit)
-
- def toggleEIPAct(self):
- # this is too simple by now.
- # XXX get STATUS CONSTANTS INSTEAD
-
- icon_status = self.conductor.get_icon_name()
- if icon_status == "connected":
- self.connAct.setEnabled(True)
- self.connAct.setText(
- self.tr('Encryption ON turn o&ff'))
- return
- if icon_status == "disconnected":
- self.connAct.setEnabled(True)
- self.connAct.setText(
- self.tr('Encryption OFF turn &on'))
- return
- if icon_status == "connecting":
- self.connAct.setDisabled(True)
- self.connAct.setText(self.tr('connecting...'))
- return
-
- def detailsWin(self):
- visible = self.isVisible()
- if visible:
- self.hide()
- else:
- self.show()
- if sys.platform == "darwin":
- self.raise_()
-
- def about(self):
- # move to widget
- flavor = BRANDING.get('short_name', None)
- content = self.tr(
- ("LEAP client<br>"
- "(version <b>%s</b>)<br>" % VERSION))
- if flavor:
- content = content + ('<br>Flavor: <i>%s</i><br>' % flavor)
- content = content + (
- "<br><a href='https://leap.se/'>"
- "https://leap.se</a>")
- QtGui.QMessageBox.about(self, self.tr("About"), content)
-
- def setConnWidget(self, icon_name):
- oldlayout = self.statusIconBox.layout()
-
- for i in range(3):
- oldlayout.itemAt(i).widget().hide()
- new = self.states[icon_name]
- oldlayout.itemAt(new).widget().show()
-
- def setIcon(self, name):
- icon_fun = self.Icons.get(name)
- if icon_fun and callable(icon_fun):
- icon = icon_fun(self)
- self.trayIcon.setIcon(icon)
-
- def getIcon(self, icon_name):
- return self.states.get(icon_name, None)
-
- def setIconToolTip(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
- """
- if reason in (QtGui.QSystemTrayIcon.Trigger,
- QtGui.QSystemTrayIcon.DoubleClick):
- context_menu = self.trayIcon.contextMenu()
- # for some reason, context_menu.show()
- # is failing in a way beyond my understanding.
- # (not working the first time it's clicked).
- # this works however.
- # XXX in osx it shows some glitches.
- context_menu.exec_(self.trayIcon.geometry().center())
-
- @QtCore.pyqtSlot()
- def onTimerTick(self):
- self.statusUpdate()
-
- @QtCore.pyqtSlot(object)
- def onOpenVPNStatusChange(self, status):
- """
- updates icon, according to the openvpn status change.
- """
- icon_name = self.conductor.get_icon_name()
- if not icon_name:
- return
-
- # XXX refactor. Use QStateMachine
-
- if icon_name in ("disconnected", "connected"):
- self.eipStatusChange.emit(icon_name)
-
- if icon_name in ("connecting"):
- # let's see how it matches
- leap_status_name = self.conductor.get_leap_status()
- self.eipStatusChange.emit(leap_status_name)
-
- if icon_name == "connected":
- # When we change to "connected', we launch
- # the network checker.
- self.initNetworkChecker.emit()
-
- self.setIcon(icon_name)
- # change connection pixmap widget
- self.setConnWidget(icon_name)
-
- @QtCore.pyqtSlot(str)
- def onEIPConnStatusChange(self, newstatus):
- """
- slot for EIP status changes
- not to be confused with onOpenVPNStatusChange.
- this only updates the non-debug LEAP Status line
- next to the connection icon.
- """
- # XXX move bold to style sheet
- self.leapConnStatus.setText(
- "<b>%s</b>" % newstatus)
diff --git a/src/leap/certs/__init__.py b/src/leap/certs/__init__.py
deleted file mode 100644
index c4d009b1..00000000
--- a/src/leap/certs/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import os
-
-_where = os.path.split(__file__)[0]
-
-
-def where(filename):
- return os.path.join(_where, filename)
diff --git a/src/leap/crypto/__init__.py b/src/leap/crypto/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/crypto/__init__.py
+++ /dev/null
diff --git a/src/leap/crypto/certs.py b/src/leap/crypto/certs.py
deleted file mode 100644
index cbb5725a..00000000
--- a/src/leap/crypto/certs.py
+++ /dev/null
@@ -1,112 +0,0 @@
-import logging
-import os
-from StringIO import StringIO
-import ssl
-import time
-
-from dateutil.parser import parse
-from OpenSSL import crypto
-
-from leap.util.misc import null_check
-
-logger = logging.getLogger(__name__)
-
-
-class BadCertError(Exception):
- """
- raised for malformed certs
- """
-
-
-class NoCertError(Exception):
- """
- raised for cert not found in given path
- """
-
-
-def get_https_cert_from_domain(domain, port=443):
- """
- @param domain: a domain name to get a certificate from.
- """
- cert = ssl.get_server_certificate((domain, port))
- x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
- return x509
-
-
-def get_cert_from_file(_file):
- null_check(_file, "pem file")
- if isinstance(_file, (str, unicode)):
- if not os.path.isfile(_file):
- raise NoCertError
- with open(_file) as f:
- cert = f.read()
- else:
- cert = _file.read()
- x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
- return x509
-
-
-def get_pkey_from_file(_file):
- getkey = lambda f: crypto.load_privatekey(
- crypto.FILETYPE_PEM, f.read())
-
- if isinstance(_file, str):
- with open(_file) as f:
- key = getkey(f)
- else:
- key = getkey(_file)
- return key
-
-
-def can_load_cert_and_pkey(string):
- """
- loads certificate and private key from
- a buffer
- """
- try:
- f = StringIO(string)
- cert = get_cert_from_file(f)
-
- f = StringIO(string)
- key = get_pkey_from_file(f)
-
- null_check(cert, 'certificate')
- null_check(key, 'private key')
- except Exception as exc:
- logger.error(type(exc), exc.message)
- raise BadCertError
- else:
- return True
-
-
-def get_cert_fingerprint(domain=None, port=443, filepath=None,
- hash_type="SHA256", sep=":"):
- """
- @param domain: a domain name to get a fingerprint from
- @type domain: str
- @param filepath: path to a file containing a PEM file
- @type filepath: str
- @param hash_type: the hash function to be used in the fingerprint.
- must be one of SHA1, SHA224, SHA256, SHA384, SHA512
- @type hash_type: str
- @rparam: hex_fpr, a hexadecimal representation of a bytestring
- containing the fingerprint.
- @rtype: string
- """
- if domain:
- cert = get_https_cert_from_domain(domain, port=port)
- if filepath:
- cert = get_cert_from_file(filepath)
- hex_fpr = cert.digest(hash_type)
- return hex_fpr
-
-
-def get_time_boundaries(certfile):
- cert = get_cert_from_file(certfile)
- null_check(cert, 'certificate')
-
- fromts, tots = (cert.get_notBefore(), cert.get_notAfter())
- from_, to_ = map(
- lambda ts: time.gmtime(time.mktime(parse(ts).timetuple())),
- (fromts, tots))
- return from_, to_
diff --git a/src/leap/crypto/certs_gnutls.py b/src/leap/crypto/certs_gnutls.py
deleted file mode 100644
index 20c0e043..00000000
--- a/src/leap/crypto/certs_gnutls.py
+++ /dev/null
@@ -1,112 +0,0 @@
-'''
-We're using PyOpenSSL now
-
-import ctypes
-from StringIO import StringIO
-import socket
-
-import gnutls.connection
-import gnutls.crypto
-import gnutls.library
-
-from leap.util.misc import null_check
-
-
-class BadCertError(Exception):
- """raised for malformed certs"""
-
-
-def get_https_cert_from_domain(domain):
- """
- @param domain: a domain name to get a certificate from.
- """
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- cred = gnutls.connection.X509Credentials()
-
- session = gnutls.connection.ClientSession(sock, cred)
- session.connect((domain, 443))
- session.handshake()
- cert = session.peer_certificate
- return cert
-
-
-def get_cert_from_file(_file):
- getcert = lambda f: gnutls.crypto.X509Certificate(f.read())
- if isinstance(_file, str):
- with open(_file) as f:
- cert = getcert(f)
- else:
- cert = getcert(_file)
- return cert
-
-
-def get_pkey_from_file(_file):
- getkey = lambda f: gnutls.crypto.X509PrivateKey(f.read())
- if isinstance(_file, str):
- with open(_file) as f:
- key = getkey(f)
- else:
- key = getkey(_file)
- return key
-
-
-def can_load_cert_and_pkey(string):
- try:
- f = StringIO(string)
- cert = get_cert_from_file(f)
-
- f = StringIO(string)
- key = get_pkey_from_file(f)
-
- null_check(cert, 'certificate')
- null_check(key, 'private key')
- except:
- # XXX catch GNUTLSError?
- raise BadCertError
- else:
- return True
-
-def get_cert_fingerprint(domain=None, filepath=None,
- hash_type="SHA256", sep=":"):
- """
- @param domain: a domain name to get a fingerprint from
- @type domain: str
- @param filepath: path to a file containing a PEM file
- @type filepath: str
- @param hash_type: the hash function to be used in the fingerprint.
- must be one of SHA1, SHA224, SHA256, SHA384, SHA512
- @type hash_type: str
- @rparam: hex_fpr, a hexadecimal representation of a bytestring
- containing the fingerprint.
- @rtype: string
- """
- if domain:
- cert = get_https_cert_from_domain(domain)
- if filepath:
- cert = get_cert_from_file(filepath)
-
- _buffer = ctypes.create_string_buffer(64)
- buffer_length = ctypes.c_size_t(64)
-
- SUPPORTED_DIGEST_FUN = ("SHA1", "SHA224", "SHA256", "SHA384", "SHA512")
- if hash_type in SUPPORTED_DIGEST_FUN:
- digestfunction = getattr(
- gnutls.library.constants,
- "GNUTLS_DIG_%s" % hash_type)
- else:
- # XXX improperlyconfigured or something
- raise Exception("digest function not supported")
-
- gnutls.library.functions.gnutls_x509_crt_get_fingerprint(
- cert._c_object, digestfunction,
- ctypes.byref(_buffer), ctypes.byref(buffer_length))
-
- # deinit
- #server_cert._X509Certificate__deinit(server_cert._c_object)
- # needed? is segfaulting
-
- fpr = ctypes.string_at(_buffer, buffer_length.value)
- hex_fpr = sep.join(u"%02X" % ord(char) for char in fpr)
-
- return hex_fpr
-'''
diff --git a/src/leap/crypto/leapkeyring.py b/src/leap/crypto/leapkeyring.py
deleted file mode 100644
index c241d0bc..00000000
--- a/src/leap/crypto/leapkeyring.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import keyring
-
-from leap.base.config import get_config_file
-
-#############
-# Disclaimer
-#############
-# This currently is not a keyring, it's more like a joke.
-# No, seriously.
-# We're affected by this **bug**
-
-# https://bitbucket.org/kang/python-keyring-lib/
-# issue/65/dbusexception-method-opensession-with
-
-# so using the gnome keyring does not seem feasible right now.
-# I thought this was the next best option to store secrets in plain sight.
-
-# in the future we should move to use the gnome/kde/macosx/win keyrings.
-
-
-class LeapCryptedFileKeyring(keyring.backend.CryptedFileKeyring):
-
- filename = ".secrets"
-
- @property
- def file_path(self):
- return get_config_file(self.filename)
-
- def __init__(self, seed=None):
- self.seed = seed
-
- def _get_new_password(self):
- # XXX every time this method is called,
- # $deity kills a kitten.
- return "secret%s" % self.seed
-
- def _init_file(self):
- self.keyring_key = self._get_new_password()
- self.set_password('keyring_setting', 'pass_ref', 'pass_ref_value')
-
- def _unlock(self):
- self.keyring_key = self._get_new_password()
- print 'keyring key ', self.keyring_key
- try:
- ref_pw = self.get_password(
- 'keyring_setting',
- 'pass_ref')
- print 'ref pw ', ref_pw
- assert ref_pw == "pass_ref_value"
- except AssertionError:
- self._lock()
- raise ValueError('Incorrect password')
-
-
-def leap_set_password(key, value, seed="xxx"):
- key, value = map(unicode, (key, value))
- keyring.set_keyring(LeapCryptedFileKeyring(seed=seed))
- keyring.set_password('leap', key, value)
-
-
-def leap_get_password(key, seed="xxx"):
- keyring.set_keyring(LeapCryptedFileKeyring(seed=seed))
- #import ipdb;ipdb.set_trace()
- return keyring.get_password('leap', key)
-
-
-if __name__ == "__main__":
- leap_set_password('test', 'bar')
- passwd = leap_get_password('test')
- assert passwd == 'bar'
diff --git a/src/leap/crypto/tests/__init__.py b/src/leap/crypto/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/crypto/tests/__init__.py
+++ /dev/null
diff --git a/src/leap/crypto/tests/test_certs.py b/src/leap/crypto/tests/test_certs.py
deleted file mode 100644
index e476b630..00000000
--- a/src/leap/crypto/tests/test_certs.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import unittest
-
-from leap.testing.https_server import where
-from leap.crypto import certs
-
-
-class CertTestCase(unittest.TestCase):
-
- def test_can_load_client_and_pkey(self):
- with open(where('leaptestscert.pem')) as cf:
- cs = cf.read()
- with open(where('leaptestskey.pem')) as kf:
- ks = kf.read()
- certs.can_load_cert_and_pkey(cs + ks)
-
- with self.assertRaises(certs.BadCertError):
- # screw header
- certs.can_load_cert_and_pkey(cs.replace("BEGIN", "BEGINN") + ks)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py
deleted file mode 100644
index 9a34a428..00000000
--- a/src/leap/eip/checks.py
+++ /dev/null
@@ -1,537 +0,0 @@
-import logging
-import time
-import os
-import sys
-
-import requests
-
-from leap import __branding as BRANDING
-from leap import certs as leapcerts
-from leap.base.auth import srpauth_protected, magick_srpauth
-from leap.base import config as baseconfig
-from leap.base import constants as baseconstants
-from leap.base import providers
-from leap.crypto import certs
-from leap.eip import config as eipconfig
-from leap.eip import constants as eipconstants
-from leap.eip import exceptions as eipexceptions
-from leap.eip import specs as eipspecs
-from leap.util.certs import get_mac_cabundle
-from leap.util.fileutil import mkdir_p
-from leap.util.web import get_https_domain_and_port
-
-logger = logging.getLogger(name=__name__)
-
-"""
-ProviderCertChecker
--------------------
-Checks on certificates. To be moved to base.
-docs TBD
-
-EIPConfigChecker
-----------
-It is used from the eip conductor (a instance of EIPConnection that is
-managed from the QtApp), running `run_all` method before trying to call
-`connect` or any other of the state-changing methods.
-
-It checks that the needed files are provided or can be discovered over the
-net. Much of these tests are not specific to EIP module, and can be splitted
-into base.tests to be invoked by the base leap init routines.
-However, I'm testing them alltogether for the sake of having the whole unit
-reachable and testable as a whole.
-
-"""
-
-
-def get_branding_ca_cert(domain):
- # deprecated
- ca_file = BRANDING.get('provider_ca_file')
- if ca_file:
- return leapcerts.where(ca_file)
-
-
-class ProviderCertChecker(object):
- """
- Several checks needed for getting
- client certs and checking tls connection
- with provider.
- """
- def __init__(self, fetcher=requests,
- domain=None):
-
- self.fetcher = fetcher
- self.domain = domain
- #XXX needs some kind of autoinit
- #right now we set by hand
- #by loading and reading provider config
- self.apidomain = None
- self.cacert = eipspecs.provider_ca_path(domain)
-
- def run_all(
- self, checker=None,
- skip_download=False, skip_verify=False):
-
- if not checker:
- checker = self
-
- do_verify = not skip_verify
- logger.debug('do_verify: %s', do_verify)
- # checker.download_ca_cert()
-
- # For MVS+
- # checker.download_ca_signature()
- # checker.get_ca_signatures()
- # checker.is_there_trust_path()
-
- # For MVS
- checker.is_there_provider_ca()
-
- checker.is_https_working(verify=do_verify, autocacert=False)
- checker.check_new_cert_needed(verify=do_verify)
-
- def download_ca_cert(self, uri=None, verify=True):
- req = self.fetcher.get(uri, verify=verify)
- req.raise_for_status()
-
- # should check domain exists
- capath = self._get_ca_cert_path(self.domain)
- with open(capath, 'w') as f:
- f.write(req.content)
-
- def check_ca_cert_fingerprint(
- self, hash_type="SHA256",
- fingerprint=None):
- """
- compares the fingerprint in
- the ca cert with a string
- we are passed
- returns True if they are equal, False if not.
- @param hash_type: digest function
- @type hash_type: str
- @param fingerprint: the fingerprint to compare with.
- @type fingerprint: str (with : separator)
- @rtype bool
- """
- ca_cert_path = self.ca_cert_path
- ca_cert_fpr = certs.get_cert_fingerprint(
- filepath=ca_cert_path)
- return ca_cert_fpr == fingerprint
-
- def verify_api_https(self, uri):
- assert uri.startswith('https://')
- cacert = self.ca_cert_path
- verify = cacert and cacert or True
- req = self.fetcher.get(uri, verify=verify)
- req.raise_for_status()
- return True
-
- def download_ca_signature(self):
- # MVS+
- raise NotImplementedError
-
- def get_ca_signatures(self):
- # MVS+
- raise NotImplementedError
-
- def is_there_trust_path(self):
- # MVS+
- raise NotImplementedError
-
- def is_there_provider_ca(self):
- if not self.cacert:
- return False
- cacert_exists = os.path.isfile(self.cacert)
- if cacert_exists:
- logger.debug('True')
- return True
- logger.debug('False!')
- return False
-
- def is_https_working(
- self, uri=None, verify=True,
- autocacert=False):
- if uri is None:
- uri = self._get_root_uri()
- # XXX raise InsecureURI or something better
- try:
- assert uri.startswith('https')
- except AssertionError:
- raise AssertionError(
- "uri passed should start with https")
- if autocacert and verify is True and self.cacert is not None:
- logger.debug('verify cert: %s', self.cacert)
- verify = self.cacert
- if sys.platform == "darwin":
- verify = get_mac_cabundle()
- logger.debug('checking https connection')
- logger.debug('uri: %s (verify:%s)', uri, verify)
-
- try:
- self.fetcher.get(uri, verify=verify)
-
- except requests.exceptions.SSLError as exc:
- raise eipexceptions.HttpsBadCertError
-
- except requests.exceptions.ConnectionError:
- logger.error('ConnectionError')
- raise eipexceptions.HttpsNotSupported
-
- else:
- return True
-
- def check_new_cert_needed(self, skip_download=False, verify=True):
- # XXX add autocacert
- if not self.is_cert_valid(do_raise=False):
- logger.debug('cert needed: true')
- self.download_new_client_cert(
- skip_download=skip_download,
- verify=verify)
- return True
- logger.debug('cert needed: false')
- return False
-
- def download_new_client_cert(self, uri=None, verify=True,
- skip_download=False,
- credentials=None):
- logger.debug('download new client cert')
- if skip_download:
- return True
- if uri is None:
- uri = self._get_client_cert_uri()
- # XXX raise InsecureURI or something better
- #assert uri.startswith('https')
-
- if verify is True and self.cacert is not None:
- verify = self.cacert
- logger.debug('verify = %s', verify)
-
- fgetfn = self.fetcher.get
-
- if credentials:
- user, passwd = credentials
- logger.debug('apidomain = %s', self.apidomain)
-
- @srpauth_protected(user, passwd,
- server="https://%s" % self.apidomain,
- verify=verify)
- def getfn(*args, **kwargs):
- return fgetfn(*args, **kwargs)
-
- else:
- # XXX FIXME fix decorated args
- @magick_srpauth(verify)
- def getfn(*args, **kwargs):
- return fgetfn(*args, **kwargs)
- try:
-
- req = getfn(uri, verify=verify)
- req.raise_for_status()
-
- except requests.exceptions.SSLError:
- logger.warning('SSLError while fetching cert. '
- 'Look below for stack trace.')
- # XXX raise better exception
- return self.fail("SSLError")
- except Exception as exc:
- return self.fail(exc.message)
-
- try:
- logger.debug('validating cert...')
- pemfile_content = req.content
- valid = self.is_valid_pemfile(pemfile_content)
- if not valid:
- logger.warning('invalid cert')
- return False
- cert_path = self._get_client_cert_path()
- self.write_cert(pemfile_content, to=cert_path)
- except:
- logger.warning('Error while validating cert')
- raise
- return True
-
- def is_cert_valid(self, cert_path=None, do_raise=True):
- exists = lambda: self.is_certificate_exists()
- valid_pemfile = lambda: self.is_valid_pemfile()
- not_expired = lambda: self.is_cert_not_expired()
-
- valid = exists() and valid_pemfile() and not_expired()
- if not valid:
- if do_raise:
- raise Exception('missing valid cert')
- else:
- return False
- return True
-
- def is_certificate_exists(self, certfile=None):
- if certfile is None:
- certfile = self._get_client_cert_path()
- return os.path.isfile(certfile)
-
- def is_cert_not_expired(self, certfile=None, now=time.gmtime):
- if certfile is None:
- certfile = self._get_client_cert_path()
- from_, to_ = certs.get_time_boundaries(certfile)
-
- return from_ < now() < to_
-
- def is_valid_pemfile(self, cert_s=None):
- """
- checks that the passed string
- is a valid pem certificate
- @param cert_s: string containing pem content
- @type cert_s: string
- @rtype: bool
- """
- if cert_s is None:
- certfile = self._get_client_cert_path()
- with open(certfile) as cf:
- cert_s = cf.read()
- try:
- valid = certs.can_load_cert_and_pkey(cert_s)
- except certs.BadCertError:
- logger.warning("Not valid pemfile")
- valid = False
- return valid
-
- @property
- def ca_cert_path(self):
- return self._get_ca_cert_path(self.domain)
-
- def _get_root_uri(self):
- return u"https://%s/" % self.domain
-
- def _get_client_cert_uri(self):
- return "https://%s/1/cert" % self.apidomain
-
- def _get_client_cert_path(self):
- return eipspecs.client_cert_path(domain=self.domain)
-
- def _get_ca_cert_path(self, domain):
- # XXX this folder path will be broken for win
- # and this should be moved to eipspecs.ca_path
-
- # XXX use baseconfig.get_provider_path(folder=Foo)
- # !!!
-
- capath = baseconfig.get_config_file(
- 'cacert.pem',
- folder='providers/%s/keys/ca' % domain)
- folder, fname = os.path.split(capath)
- if not os.path.isdir(folder):
- mkdir_p(folder)
- return capath
-
- def write_cert(self, pemfile_content, to=None):
- folder, filename = os.path.split(to)
- if not os.path.isdir(folder):
- mkdir_p(folder)
- with open(to, 'w') as cert_f:
- cert_f.write(pemfile_content)
-
- def set_api_domain(self, domain):
- self.apidomain = domain
-
-
-class EIPConfigChecker(object):
- """
- Several checks needed
- to ensure a EIPConnection
- can be sucessfully established.
- use run_all to run all checks.
- """
-
- def __init__(self, fetcher=requests, domain=None):
- # we do not want to accept too many
- # argument on init.
- # we want tests
- # to be explicitely run.
-
- self.fetcher = fetcher
-
- # if not domain, get from config
- self.domain = domain
- self.apidomain = None
- self.cacert = eipspecs.provider_ca_path(domain)
-
- self.defaultprovider = providers.LeapProviderDefinition(domain=domain)
- self.defaultprovider.load()
- self.eipconfig = eipconfig.EIPConfig(domain=domain)
- self.set_api_domain()
- self.eipserviceconfig = eipconfig.EIPServiceConfig(domain=domain)
- self.eipserviceconfig.load()
-
- def run_all(self, checker=None, skip_download=False):
- """
- runs all checks in a row.
- will raise if some error encountered.
- catching those exceptions is not
- our responsibility at this moment
- """
- if not checker:
- checker = self
-
- # let's call all tests
- # needed for a sane eip session.
-
- # TODO: get rid of check_default.
- # check_complete should
- # be enough. but here to make early tests easier.
- checker.check_default_eipconfig()
-
- checker.check_is_there_default_provider()
- checker.fetch_definition(skip_download=skip_download)
- checker.fetch_eip_service_config(skip_download=skip_download)
- checker.check_complete_eip_config()
- #checker.ping_gateway()
-
- # public checks
-
- def check_default_eipconfig(self):
- """
- checks if default eipconfig exists,
- and dumps a default file if not
- """
- # XXX ONLY a transient check
- # because some old function still checks
- # for eip config at the beginning.
-
- # it *really* does not make sense to
- # dump it right now, we can get an in-memory
- # config object and dump it to disk in a
- # later moment
- logger.debug('checking default eip config')
- if not self._is_there_default_eipconfig():
- self._dump_default_eipconfig()
-
- def check_is_there_default_provider(self, config=None):
- """
- raises EIPMissingDefaultProvider if no
- default provider found on eip config.
- This is catched by ui and runs FirstRunWizard (MVS+)
- """
- if config is None:
- config = self.eipconfig.config
- logger.debug('checking default provider')
- provider = config.get('provider', None)
- if provider is None:
- raise eipexceptions.EIPMissingDefaultProvider
- # XXX raise also if malformed ProviderDefinition?
- return True
-
- def fetch_definition(self, skip_download=False,
- force_download=False,
- config=None, uri=None,
- domain=None):
- """
- fetches a definition file from server
- """
- # TODO:
- # - Implement diff
- # - overwrite only if different.
- # (attend to serial field different, for instance)
-
- logger.debug('fetching definition')
-
- if skip_download:
- logger.debug('(fetching def skipped)')
- return True
- if config is None:
- config = self.defaultprovider.config
- if uri is None:
- if not domain:
- domain = config.get('provider', None)
- uri = self._get_provider_definition_uri(domain=domain)
-
- if sys.platform == "darwin":
- verify = get_mac_cabundle()
- else:
- verify = True
-
- self.defaultprovider.load(
- from_uri=uri,
- fetcher=self.fetcher,
- verify=verify)
- self.defaultprovider.save()
-
- def fetch_eip_service_config(self, skip_download=False,
- force_download=False,
- config=None, uri=None, # domain=None,
- autocacert=True, verify=True):
- if skip_download:
- return True
- if config is None:
- self.eipserviceconfig.load()
- config = self.eipserviceconfig.config
- if uri is None:
- #XXX
- #if not domain:
- #domain = self.domain or config.get('provider', None)
- uri = self._get_eip_service_uri(
- domain=self.apidomain)
-
- if autocacert and self.cacert is not None:
- verify = self.cacert
-
- self.eipserviceconfig.load(
- from_uri=uri,
- fetcher=self.fetcher,
- force_download=force_download,
- verify=verify)
- self.eipserviceconfig.save()
-
- def check_complete_eip_config(self, config=None):
- # TODO check for gateway
- if config is None:
- config = self.eipconfig.config
- try:
- assert 'provider' in config
- assert config['provider'] is not None
- # XXX assert there is gateway !!
- except AssertionError:
- raise eipexceptions.EIPConfigurationError
-
- # XXX TODO:
- # We should WRITE eip config if missing or
- # incomplete at this point
- #self.eipconfig.save()
-
- #
- # private helpers
- #
-
- def _is_there_default_eipconfig(self):
- return self.eipconfig.exists()
-
- def _dump_default_eipconfig(self):
- self.eipconfig.save(force=True)
-
- def _get_provider_definition_uri(self, domain=None, path=None):
- if domain is None:
- domain = self.domain or baseconstants.DEFAULT_PROVIDER
- if path is None:
- path = baseconstants.DEFINITION_EXPECTED_PATH
- uri = u"https://%s/%s" % (domain, path)
- logger.debug('getting provider definition from %s' % uri)
- return uri
-
- def _get_eip_service_uri(self, domain=None, path=None):
- if domain is None:
- domain = self.domain or baseconstants.DEFAULT_PROVIDER
- if path is None:
- path = eipconstants.EIP_SERVICE_EXPECTED_PATH
- uri = "https://%s/%s" % (domain, path)
- logger.debug('getting eip service file from %s', uri)
- return uri
-
- def set_api_domain(self):
- """sets api domain from defaultprovider config object"""
- api = self.defaultprovider.config.get('api_uri', None)
- # the caller is responsible for having loaded the config
- # object at this point
- if api:
- api_dom = get_https_domain_and_port(api)
- self.apidomain = "%s:%s" % api_dom
-
- def get_api_domain(self):
- """gets api domain"""
- return self.apidomain
diff --git a/src/leap/eip/conductor.py b/src/leap/eip/conductor.py
new file mode 100644
index 00000000..8f9d6051
--- /dev/null
+++ b/src/leap/eip/conductor.py
@@ -0,0 +1,340 @@
+"""
+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.util.coroutines import spawn_and_watch_process
+
+# XXX from leap.eip import config as eipconfig
+# from leap.eip import exceptions as eip_exceptions
+
+from leap.eip.config import (get_config, build_ovpn_command,
+ check_or_create_default_vpnconf,
+ check_vpn_keys,
+ EIPNoPkexecAvailable,
+ EIPNoPolkitAuthAgentAvailable,
+ EIPInitNoProviderError,
+ EIPInitBadProviderError,
+ EIPInitNoKeyFileError,
+ EIPInitBadKeyFilePermError)
+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
+# eip.exceptions
+
+class EIPNoCommandError(Exception):
+ pass
+
+
+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
+ """
+ # XXX we should catch this and raise
+ # to qtland, so we emit signal
+ # to translate whatever kind of error
+ # to user-friendly msg in dialog.
+ pass
+
+#
+# Openvpn related classes
+#
+
+
+class OpenVPNConnection(object):
+ """
+ All related to invocation
+ of the openvpn binary
+ """
+ # Connection Methods
+
+ def __init__(self, config_file=None,
+ watcher_cb=None, debug=False):
+ #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.debug = debug
+ #print('conductor:%s' % debug)
+
+ 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.missing_pkexec = False
+ self.missing_auth_agent = False
+ self.bad_keyfile_perms = False
+ self.missing_vpn_keyfile = False
+ self.missing_provider = False
+ self.bad_provider = False
+
+ self.command = None
+ self.args = None
+
+ self.autostart = True
+ self._get_or_create_config()
+ self._check_vpn_keys()
+
+ def _set_autostart(self):
+ config = self.config
+ if config.has_option('openvpn', 'autostart'):
+ autostart = config.getboolean('openvpn',
+ 'autostart')
+ self.autostart = autostart
+ else:
+ if config.has_option('DEFAULT', 'autostart'):
+ autostart = config.getboolean('DEFAULT',
+ 'autostart')
+ self.autostart = autostart
+
+ def _set_ovpn_command(self):
+ config = self.config
+ if config.has_option('openvpn', 'command'):
+ commandline = config.get('openvpn', 'command')
+
+ command_split = commandline.split(' ')
+ command = command_split[0]
+ if len(command_split) > 1:
+ args = command_split[1:]
+ else:
+ args = []
+
+ self.command = command
+ self.args = args
+ else:
+ # no command in config, we build it up.
+ # XXX check also for command-line --command flag
+ try:
+ command, args = build_ovpn_command(config,
+ debug=self.debug)
+ except EIPNoPolkitAuthAgentAvailable:
+ command = args = None
+ self.missing_auth_agent = True
+ except EIPNoPkexecAvailable:
+ command = args = None
+ self.missing_pkexec = True
+
+ # XXX if not command, signal error.
+ self.command = command
+ self.args = args
+
+ def _check_ovpn_config(self):
+ """
+ checks if there is a default openvpn config.
+ if not, it writes one with info from the provider
+ definition file
+ """
+ # TODO
+ # - get --with-openvpn-config from opts
+ try:
+ check_or_create_default_vpnconf(self.config)
+ except EIPInitNoProviderError:
+ logger.error('missing default provider definition')
+ self.missing_provider = True
+ except EIPInitBadProviderError:
+ logger.error('bad provider definition')
+ self.bad_provider = True
+
+ def _get_or_create_config(self):
+ """
+ retrieves the config options from defaults or
+ home file, or config file passed in command line.
+ populates command and args to be passed to subprocess.
+ """
+ config = get_config(config_file=self.config_file)
+ self.config = config
+
+ self._set_autostart()
+ self._set_ovpn_command()
+ self._check_ovpn_config()
+
+ def _check_vpn_keys(self):
+ """
+ checks for correct permissions on vpn keys
+ """
+ try:
+ check_vpn_keys(self.config)
+ except EIPInitNoKeyFileError:
+ self.missing_vpn_keyfile = True
+ except EIPInitBadKeyFilePermError:
+ logger.error('error while checking vpn keys')
+ self.bad_keyfile_perms = True
+
+ 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.command is None:
+ raise EIPNoCommandError
+ 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 here?
+
+ def disconnect(self):
+ """
+ disconnects client
+ """
+ self._disconnect()
+ self.status.change_to(self.status.DISCONNECTED)
+
+ 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/config.py b/src/leap/eip/config.py
index 917871da..6118c9de 100644
--- a/src/leap/eip/config.py
+++ b/src/leap/eip/config.py
@@ -1,153 +1,186 @@
+import ConfigParser
+import grp
import logging
import os
import platform
-import re
-import tempfile
+import socket
-from leap import __branding as BRANDING
-from leap import certs
-from leap.util.misc import null_check
-from leap.util.fileutil import (which, mkdir_p, check_and_fix_urw_only)
-
-from leap.base import config as baseconfig
+from leap.util.fileutil import (which, mkdir_p,
+ check_and_fix_urw_only)
from leap.baseapp.permcheck import (is_pkexec_in_system,
is_auth_agent_running)
-from leap.eip import exceptions as eip_exceptions
-from leap.eip import specs as eipspecs
logger = logging.getLogger(name=__name__)
-provider_ca_file = BRANDING.get('provider_ca_file', None)
+logger.setLevel('DEBUG')
+
+# XXX move exceptions to
+# from leap.eip import exceptions as eip_exceptions
+
+
+class EIPNoPkexecAvailable(Exception):
+ pass
+
-_platform = platform.system()
+class EIPNoPolkitAuthAgentAvailable(Exception):
+ pass
-class EIPConfig(baseconfig.JSONLeapConfig):
- spec = eipspecs.eipconfig_spec
+class EIPInitNoProviderError(Exception):
+ pass
- def _get_slug(self):
- eipjsonpath = baseconfig.get_config_file(
- 'eip.json')
- return eipjsonpath
- def _set_slug(self, *args, **kwargs):
- raise AttributeError("you cannot set slug")
+class EIPInitBadProviderError(Exception):
+ pass
- slug = property(_get_slug, _set_slug)
+class EIPInitNoKeyFileError(Exception):
+ pass
-class EIPServiceConfig(baseconfig.JSONLeapConfig):
- spec = eipspecs.eipservice_config_spec
- def _get_slug(self):
- domain = getattr(self, 'domain', None)
- if domain:
- path = baseconfig.get_provider_path(domain)
- else:
- path = baseconfig.get_default_provider_path()
- return baseconfig.get_config_file(
- 'eip-service.json', folder=path)
+class EIPInitBadKeyFilePermError(Exception):
+ pass
- def _set_slug(self):
- raise AttributeError("you cannot set slug")
- slug = property(_get_slug, _set_slug)
+OPENVPN_CONFIG_TEMPLATE = """#Autogenerated by eip-client wizard
+remote {VPN_REMOTE_HOST} {VPN_REMOTE_PORT}
+client
+dev tun
+persist-tun
+persist-key
+proto udp
+tls-client
+remote-cert-tls server
-def get_socket_path():
- socket_path = os.path.join(
- tempfile.mkdtemp(prefix="leap-tmp"),
- 'openvpn.socket')
- #logger.debug('socket path: %s', socket_path)
- return socket_path
+cert {LEAP_EIP_KEYS}
+key {LEAP_EIP_KEYS}
+ca {LEAP_EIP_KEYS}
+"""
-def get_eip_gateway(eipconfig=None, eipserviceconfig=None):
+def get_config_dir():
"""
- return the first host in eip service config
- that matches the name defined in the eip.json config
- file.
+ get the base dir for all leap config
+ @rparam: config path
+ @rtype: string
"""
- # XXX eventually we should move to a more clever
- # gateway selection. maybe we could return
- # all gateways that match our cluster.
-
- null_check(eipconfig, "eipconfig")
- null_check(eipserviceconfig, "eipserviceconfig")
- PLACEHOLDER = "testprovider.example.org"
-
- conf = eipconfig.config
- eipsconf = eipserviceconfig.config
-
- primary_gateway = conf.get('primary_gateway', None)
- if not primary_gateway:
- return PLACEHOLDER
-
- gateways = eipsconf.get('gateways', None)
- if not gateways:
- logger.error('missing gateways in eip service config')
- return PLACEHOLDER
-
- if len(gateways) > 0:
- for gw in gateways:
- clustername = gw.get('cluster', None)
- if not clustername:
- logger.error('no cluster name')
- return
-
- if clustername == primary_gateway:
- # XXX at some moment, we must
- # make this a more generic function,
- # and return ports, protocols...
- ipaddress = gw.get('ip_address', None)
- if not ipaddress:
- logger.error('no ip_address')
- return
- return ipaddress
- logger.error('could not find primary gateway in provider'
- 'gateway list')
-
-
-def get_cipher_options(eipserviceconfig=None):
+ # TODO
+ # check for $XDG_CONFIG_HOME var?
+ # get a more sensible path for win/mac
+ # kclair: opinion? ^^
+ return os.path.expanduser(
+ os.path.join('~',
+ '.config',
+ 'leap'))
+
+
+def get_config_file(filename, folder=None):
"""
- gathers optional cipher options from eip-service config.
- :param eipserviceconfig: EIPServiceConfig instance
+ concatenates the given filename
+ with leap config dir.
+ @param filename: name of the file
+ @type filename: string
+ @rparam: full path to config file
"""
- null_check(eipserviceconfig, 'eipserviceconfig')
- eipsconf = eipserviceconfig.get_config()
+ path = []
+ path.append(get_config_dir())
+ if folder is not None:
+ path.append(folder)
+ path.append(filename)
+ return os.path.join(*path)
- ALLOWED_KEYS = ("auth", "cipher", "tls-cipher")
- CIPHERS_REGEX = re.compile("[A-Z0-9\-]+")
- opts = []
- if 'openvpn_configuration' in eipsconf:
- config = eipserviceconfig.config.get(
- "openvpn_configuration", {})
- for key, value in config.items():
- if key in ALLOWED_KEYS and value is not None:
- sanitized_val = CIPHERS_REGEX.findall(value)
- if len(sanitized_val) != 0:
- _val = sanitized_val[0]
- opts.append('--%s' % key)
- opts.append('%s' % _val)
- return opts
-LINUX_UP_DOWN_SCRIPT = "/etc/leap/resolv-update"
-OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so"
+def get_default_provider_path():
+ default_subpath = os.path.join("providers",
+ "default")
+ default_provider_path = get_config_file(
+ '',
+ folder=default_subpath)
+ return default_provider_path
-def has_updown_scripts():
+def validate_ip(ip_str):
"""
- checks the existence of the up/down scripts
+ raises exception if the ip_str is
+ not a valid representation of an ip
"""
- # XXX should check permissions too
- is_file = os.path.isfile(LINUX_UP_DOWN_SCRIPT)
- if not is_file:
- logger.warning(
- "Could not find up/down scripts at %s! "
- "Risk of DNS Leaks!!!")
- return is_file
+ socket.inet_aton(ip_str)
-def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
+def check_or_create_default_vpnconf(config):
+ """
+ checks that a vpn config file
+ exists for a default provider,
+ or creates one if it does not.
+ ATM REQURES A [provider] section in
+ eip.cfg with _at least_ a remote_ip value
+ """
+ default_provider_path = get_default_provider_path()
+
+ if not os.path.isdir(default_provider_path):
+ mkdir_p(default_provider_path)
+
+ conf_file = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+
+ if os.path.isfile(conf_file):
+ return
+ else:
+ logger.debug(
+ 'missing default openvpn config\n'
+ 'creating one...')
+
+ # We're getting provider from eip.cfg
+ # by now. Get it from a list of gateways
+ # instead.
+
+ try:
+ remote_ip = config.get('provider',
+ 'remote_ip')
+ validate_ip(remote_ip)
+
+ except ConfigParser.NoOptionError:
+ raise EIPInitNoProviderError
+
+ except socket.error:
+ # this does not look like an ip, dave
+ raise EIPInitBadProviderError
+
+ if config.has_option('provider', 'remote_port'):
+ remote_port = config.get('provider',
+ 'remote_port')
+ else:
+ remote_port = 1194
+
+ default_subpath = os.path.join("providers",
+ "default")
+ default_provider_path = get_config_file(
+ '',
+ folder=default_subpath)
+
+ if not os.path.isdir(default_provider_path):
+ mkdir_p(default_provider_path)
+
+ conf_file = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+
+ # XXX keys have to be manually placed by now
+ keys_file = get_config_file(
+ 'openvpn.keys',
+ folder=default_provider_path)
+
+ ovpn_config = OPENVPN_CONFIG_TEMPLATE.format(
+ VPN_REMOTE_HOST=remote_ip,
+ VPN_REMOTE_PORT=remote_port,
+ LEAP_EIP_KEYS=keys_file)
+
+ with open(conf_file, 'wb') as f:
+ f.write(ovpn_config)
+
+
+def build_ovpn_options(daemon=False):
"""
build a list of options
to be passed in the
@@ -162,57 +195,17 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
# since we will need to take some
# things from there if present.
- provider = kwargs.pop('provider', None)
- eipconfig = EIPConfig(domain=provider)
- eipconfig.load()
- eipserviceconfig = EIPServiceConfig(domain=provider)
- eipserviceconfig.load()
-
# get user/group name
# also from config.
- user = baseconfig.get_username()
- group = baseconfig.get_groupname()
+ user = os.getlogin()
+ gid = os.getgroups()[-1]
+ group = grp.getgrgid(gid).gr_name
opts = []
- opts.append('--client')
-
- opts.append('--dev')
- # XXX same in win?
- opts.append('tun')
- opts.append('--persist-tun')
- opts.append('--persist-key')
-
- verbosity = kwargs.get('ovpn_verbosity', None)
- if verbosity and 1 <= verbosity <= 6:
- opts.append('--verb')
- opts.append("%s" % verbosity)
-
- # remote ##############################
- # (server, port, protocol)
-
- opts.append('--remote')
-
- gw = get_eip_gateway(eipconfig=eipconfig,
- eipserviceconfig=eipserviceconfig)
- logger.debug('setting eip gateway to %s', gw)
- opts.append(str(gw))
-
- # get port/protocol from eipservice too
- opts.append('1194')
- #opts.append('80')
- opts.append('udp')
-
- opts.append('--tls-client')
- opts.append('--remote-cert-tls')
- opts.append('server')
-
- # get ciphers #######################
-
- ciphers = get_cipher_options(
- eipserviceconfig=eipserviceconfig)
- for cipheropt in ciphers:
- opts.append(str(cipheropt))
+ #moved to config files
+ #opts.append('--persist-tun')
+ #opts.append('--persist-key')
# set user and group
opts.append('--user')
@@ -228,44 +221,30 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
# interface. unix sockets or telnet interface for win.
# XXX take them from the config object.
- if _platform == "Windows":
+ ourplatform = platform.system()
+ if ourplatform in ("Linux", "Mac"):
+ opts.append('--management')
+ opts.append('/tmp/.eip.sock')
+ opts.append('unix')
+ if ourplatform == "Windows":
opts.append('--management')
opts.append('localhost')
# XXX which is a good choice?
opts.append('7777')
- if _platform in ("Linux", "Darwin"):
- opts.append('--management')
+ # remaining config options will go in a file
- if socket_path is None:
- socket_path = get_socket_path()
- opts.append(socket_path)
- opts.append('unix')
+ # NOTE: we will build this file from
+ # the service definition file.
+ # XXX override from --with-openvpn-config
+
+ opts.append('--config')
- opts.append('--script-security')
- opts.append('2')
-
- if _platform == "Linux":
- if has_updown_scripts():
- opts.append("--up")
- opts.append(LINUX_UP_DOWN_SCRIPT)
- opts.append("--down")
- opts.append(LINUX_UP_DOWN_SCRIPT)
- opts.append("--plugin")
- opts.append(OPENVPN_DOWN_ROOT)
- opts.append("'script_type=down %s'" % LINUX_UP_DOWN_SCRIPT)
-
- # certs
- client_cert_path = eipspecs.client_cert_path(provider)
- ca_cert_path = eipspecs.provider_ca_path(provider)
-
- # XXX FIX paths for MAC
- opts.append('--cert')
- opts.append(client_cert_path)
- opts.append('--key')
- opts.append(client_cert_path)
- opts.append('--ca')
- opts.append(ca_cert_path)
+ default_provider_path = get_default_provider_path()
+ ovpncnf = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+ opts.append(ovpncnf)
# we cannot run in daemon mode
# with the current subp setting.
@@ -273,16 +252,17 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
#if daemon is True:
#opts.append('--daemon')
- logger.debug('vpn options: %s', ' '.join(opts))
return opts
-def build_ovpn_command(debug=False, do_pkexec_check=True, vpnbin=None,
- socket_path=None, **kwargs):
+def build_ovpn_command(config, debug=False):
"""
build a string with the
complete openvpn invocation
+ @param config: config object
+ @type config: ConfigParser instance
+
@rtype [string, [list of strings]]
@rparam: a list containing the command string
and a list of options.
@@ -291,18 +271,18 @@ def build_ovpn_command(debug=False, do_pkexec_check=True, vpnbin=None,
use_pkexec = True
ovpn = None
- # XXX get use_pkexec from config instead.
-
- if _platform == "Linux" and use_pkexec and do_pkexec_check:
+ if config.has_option('openvpn', 'use_pkexec'):
+ use_pkexec = config.get('openvpn', 'use_pkexec')
+ if platform.system() == "Linux" and use_pkexec:
- # check for both pkexec
+ # XXX check for both pkexec (done)
# AND a suitable authentication
# agent running.
logger.info('use_pkexec set to True')
if not is_pkexec_in_system():
logger.error('no pkexec in system')
- raise eip_exceptions.EIPNoPkexecAvailable
+ raise EIPNoPkexecAvailable
if not is_auth_agent_running():
logger.warning(
@@ -310,48 +290,101 @@ def build_ovpn_command(debug=False, do_pkexec_check=True, vpnbin=None,
"pkexec will use its own text "
"based authentication agent. "
"that's probably a bad idea")
- raise eip_exceptions.EIPNoPolkitAuthAgentAvailable
+ raise EIPNoPolkitAuthAgentAvailable
command.append('pkexec')
- if vpnbin is None:
- if _platform == "Darwin":
- # XXX Should hardcode our installed path
- # /Applications/LEAPClient.app/Contents/Resources/openvpn.leap
- openvpn_bin = "openvpn.leap"
- else:
- openvpn_bin = "openvpn"
- #XXX hardcode for darwin
- ovpn = which(openvpn_bin)
- else:
- ovpn = vpnbin
+ if config.has_option('openvpn',
+ 'openvpn_binary'):
+ ovpn = config.get('openvpn',
+ 'openvpn_binary')
+ if not ovpn and config.has_option('DEFAULT',
+ 'openvpn_binary'):
+ ovpn = config.get('DEFAULT',
+ 'openvpn_binary')
+
if ovpn:
- vpn_command = ovpn
- else:
- vpn_command = "openvpn"
- command.append(vpn_command)
+ command.append(ovpn)
+
daemon_mode = not debug
- for opt in build_ovpn_options(daemon=daemon_mode, socket_path=socket_path,
- **kwargs):
+ for opt in build_ovpn_options(daemon=daemon_mode):
command.append(opt)
# XXX check len and raise proper error
- if _platform == "Darwin":
- OSX_ASADMIN = 'do shell script "%s" with administrator privileges'
- # XXX fix workaround for Nones
- _command = [x if x else " " for x in command]
- # XXX debugging!
- # XXX get openvpn log path from debug flags
- _command.append('--log')
- _command.append('/tmp/leap_openvpn.log')
- return ["osascript", ["-e", OSX_ASADMIN % ' '.join(_command)]]
- else:
- return [command[0], command[1:]]
+ return [command[0], command[1:]]
+
+
+def get_sensible_defaults():
+ """
+ gathers a dict of sensible defaults,
+ platform sensitive,
+ to be used to initialize the config parser
+ @rtype: dict
+ @rparam: default options.
+ """
+ # this way we're passing a simple dict
+ # that will initialize the configparser
+ # and will get written to "DEFAULTS" section,
+ # which is fine for now.
+ # if we want to write to a particular section
+ # we can better pass a tuple of triples
+ # (('section1', 'foo', '23'),)
+ # and config.set them
-def check_vpn_keys(provider=None):
+ defaults = dict()
+ defaults['openvpn_binary'] = which('openvpn')
+ defaults['autostart'] = 'true'
+
+ # TODO
+ # - management.
+ return defaults
+
+
+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
+
+ @rtype: ConfigParser instance
+ @rparam: a config object
+ """
+ # TODO
+ # - refactor out common things and get
+ # them to util/ or baseapp/
+
+ defaults = get_sensible_defaults()
+ config = ConfigParser.ConfigParser(defaults)
+
+ if not config_file:
+ fpath = get_config_file('eip.cfg')
+ if not os.path.isfile(fpath):
+ dpath, cfile = os.path.split(fpath)
+ if not os.path.isdir(dpath):
+ mkdir_p(dpath)
+ with open(fpath, 'wb') as configfile:
+ config.write(configfile)
+ config_file = open(fpath)
+
+ #TODO
+ # - convert config_file to list;
+ # look in places like /etc/leap/eip.cfg
+ # for global settings.
+ # - raise warnings/error if bad options.
+
+ # at this point, the file should exist.
+ # errors would have been raised above.
+
+ config.readfp(config_file)
+
+ return config
+
+
+def check_vpn_keys(config):
"""
performs an existance and permission check
over the openvpn keys file.
@@ -359,40 +392,35 @@ def check_vpn_keys(provider=None):
per provider, containing the CA cert,
the provider key, and our client certificate
"""
- assert provider is not None
- provider_ca = eipspecs.provider_ca_path(provider)
- client_cert = eipspecs.client_cert_path(provider)
- logger.debug('provider ca = %s', provider_ca)
- logger.debug('client cert = %s', client_cert)
+ keyopt = ('provider', 'keyfile')
+
+ # XXX at some point,
+ # should separate between CA, provider cert
+ # and our certificate.
+ # make changes in the default provider template
+ # accordingly.
+
+ # get vpn keys
+ if config.has_option(*keyopt):
+ keyfile = config.get(*keyopt)
+ else:
+ keyfile = get_config_file(
+ 'openvpn.keys',
+ folder=get_default_provider_path())
+ logger.debug('keyfile = %s', keyfile)
# if no keys, raise error.
- # it's catched by the ui and signal user.
-
- if not os.path.isfile(provider_ca):
- # not there. let's try to copy.
- folder, filename = os.path.split(provider_ca)
- if not os.path.isdir(folder):
- mkdir_p(folder)
- if provider_ca_file:
- cacert = certs.where(provider_ca_file)
- with open(provider_ca, 'w') as pca:
- with open(cacert, 'r') as cac:
- pca.write(cac.read())
-
- if not os.path.isfile(provider_ca):
- logger.error('key file %s not found. aborting.',
- provider_ca)
- raise eip_exceptions.EIPInitNoKeyFileError
+ # should be catched by the ui and signal user.
- if not os.path.isfile(client_cert):
+ if not os.path.isfile(keyfile):
logger.error('key file %s not found. aborting.',
- client_cert)
- raise eip_exceptions.EIPInitNoKeyFileError
-
- for keyfile in (provider_ca, client_cert):
- # bad perms? try to fix them
- try:
- check_and_fix_urw_only(keyfile)
- except OSError:
- raise eip_exceptions.EIPInitBadKeyFilePermError
+ keyfile)
+ raise EIPInitNoKeyFileError
+
+ # check proper permission on keys
+ # bad perms? try to fix them
+ try:
+ check_and_fix_urw_only(keyfile)
+ except OSError:
+ raise EIPInitBadKeyFilePermError
diff --git a/src/leap/eip/constants.py b/src/leap/eip/constants.py
deleted file mode 100644
index 9af5a947..00000000
--- a/src/leap/eip/constants.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# not used anymore with the new JSONConfig.slug
-EIP_CONFIG = "eip.json"
-EIP_SERVICE_EXPECTED_PATH = "1/config/eip-service.json"
diff --git a/src/leap/eip/eipconnection.py b/src/leap/eip/eipconnection.py
deleted file mode 100644
index d012c567..00000000
--- a/src/leap/eip/eipconnection.py
+++ /dev/null
@@ -1,405 +0,0 @@
-"""
-EIP Connection Class
-"""
-from __future__ import (absolute_import,)
-import logging
-import Queue
-import sys
-import time
-
-from dateutil.parser import parse as dateparse
-
-from leap.eip.checks import ProviderCertChecker
-from leap.eip.checks import EIPConfigChecker
-from leap.eip import config as eipconfig
-from leap.eip import exceptions as eip_exceptions
-from leap.eip.openvpnconnection import OpenVPNConnection
-
-logger = logging.getLogger(name=__name__)
-
-
-class StatusMixIn(object):
-
- # a bunch of methods related with querying the connection
- # state/status and displaying useful info.
- # Needs to get clear on what is what, and
- # separate functions.
- # Should separate EIPConnectionStatus (self.status)
- # from the OpenVPN state/status command and parsing.
-
- ERR_CONNREFUSED = False
-
- def connection_state(self):
- """
- returns the current connection state
- """
- return self.status.current
-
- def get_icon_name(self):
- """
- get icon name from status object
- """
- return self.status.get_state_icon()
-
- def get_leap_status(self):
- return self.status.get_leap_status()
-
- def poll_connection_state(self):
- """
- """
- try:
- state = self.get_connection_state()
- except eip_exceptions.ConnectionRefusedError:
- # connection refused. might be not ready yet.
- if not self.ERR_CONNREFUSED:
- logger.warning('connection refused')
- self.ERR_CONNREFUSED = True
- return
- if not state:
- #logger.debug('no 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 make_error(self):
- """
- capture error and wrap it in an
- understandable format
- """
- # mostly a hack to display errors in the debug UI
- # w/o breaking the polling.
- #XXX get helpful error codes
- self.with_errors = True
- now = int(time.time())
- return '%s,LAUNCHER ERROR,ERROR,-,-' % now
-
- def state(self):
- """
- Sends 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 vpn_status(self):
- """
- OpenVPN command: status
- """
- status = self._send_command("status")
- return status
-
- def vpn_status2(self):
- """
- OpenVPN command: last 2 statuses
- """
- return self._send_command("status 2")
-
- #
- # parse info as the UI expects
- #
-
- def get_status_io(self):
- status = self.vpn_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 = dateparse(when.split(',')[1]).timetuple()
- sep = ','
- # XXX clean up this!
- 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
-
-
-class EIPConnection(OpenVPNConnection, StatusMixIn):
- """
- Aka conductor.
- Manages the execution of the OpenVPN process, auto starts, monitors the
- network connection, handles configuration, fixes leaky hosts, handles
- errors, etc.
- Status updates (connected, bandwidth, etc) are signaled to the GUI.
- """
-
- # XXX change name to EIPConductor ??
-
- def __init__(self,
- provider_cert_checker=ProviderCertChecker,
- config_checker=EIPConfigChecker,
- *args, **kwargs):
- #self.settingsfile = kwargs.get('settingsfile', None)
- #self.logfile = kwargs.get('logfile', None)
- self.provider = kwargs.pop('provider', None)
- self._providercertchecker = provider_cert_checker
- self._configchecker = config_checker
-
- self.error_queue = Queue.Queue()
-
- status_signals = kwargs.pop('status_signals', None)
- self.status = EIPConnectionStatus(callbacks=status_signals)
-
- checker_signals = kwargs.pop('checker_signals', None)
- self.checker_signals = checker_signals
-
- self.init_checkers()
-
- host = eipconfig.get_socket_path()
- kwargs['host'] = host
-
- super(EIPConnection, self).__init__(*args, **kwargs)
-
- def connect(self, **kwargs):
- """
- entry point for connection process
- """
- # in OpenVPNConnection
- self.try_openvpn_connection()
-
- def disconnect(self, shutdown=False):
- """
- disconnects client
- """
- self.terminate_openvpn_connection(shutdown=shutdown)
- self.status.change_to(self.status.DISCONNECTED)
-
- def has_errors(self):
- return True if self.error_queue.qsize() != 0 else False
-
- def init_checkers(self):
- """
- initialize checkers
- """
- self.provider_cert_checker = self._providercertchecker(
- domain=self.provider)
- self.config_checker = self._configchecker(domain=self.provider)
-
- def set_provider_domain(self, domain):
- """
- sets the provider domain.
- used from the first run wizard when we launch the run_checks
- and connect process after having initialized the conductor.
- """
- # This looks convoluted, right.
- # We have to reinstantiate checkers cause we're passing
- # the domain param that we did not know at the beginning
- # (only for the firstrunwizard case)
- self.provider = domain
- self.init_checkers()
-
- def run_checks(self, skip_download=False, skip_verify=False):
- """
- run all eip checks previous to attempting a connection
- """
- logger.debug('running conductor checks')
-
- def push_err(exc):
- # keep the original traceback!
- exc_traceback = sys.exc_info()[2]
- self.error_queue.put((exc, exc_traceback))
-
- try:
- # network (1)
- if self.checker_signals:
- for signal in self.checker_signals:
- signal('checking encryption keys')
- self.provider_cert_checker.run_all(skip_verify=skip_verify)
- except Exception as exc:
- push_err(exc)
- try:
- if self.checker_signals:
- for signal in self.checker_signals:
- signal('checking provider config')
- self.config_checker.run_all(skip_download=skip_download)
- except Exception as exc:
- push_err(exc)
- try:
- self.run_openvpn_checks()
- except Exception as exc:
- push_err(exc)
-
-
-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
- """
- self.current = self.DISCONNECTED
- self.previous = None
- # (callbacks to connect to signals in Qt-land)
- 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_leap_status(self):
- # XXX improve nomenclature
- leap_status = {
- 0: 'disconnected',
- 1: 'connecting to gateway',
- 2: 'connecting to gateway',
- 3: 'authenticating',
- 4: 'establishing network encryption',
- 5: 'establishing network encryption',
- 6: 'establishing network encryption',
- 7: 'connected',
- 8: 'reconnecting',
- 9: 'exiting',
- 11: 'unrecoverable error',
- }
- return leap_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)
diff --git a/src/leap/eip/exceptions.py b/src/leap/eip/exceptions.py
deleted file mode 100644
index b7d398c3..00000000
--- a/src/leap/eip/exceptions.py
+++ /dev/null
@@ -1,175 +0,0 @@
-"""
-Generic error hierarchy
-Leap/EIP exceptions used for exception handling,
-logging, and notifying user of errors
-during leap operation.
-
-Exception hierarchy
--------------------
-All EIP Errors must inherit from EIPClientError (note: move that to
-a more generic LEAPClientBaseError).
-
-Exception attributes and their meaning/uses
--------------------------------------------
-
-* critical: if True, will abort execution prematurely,
- after attempting any cleaning
- action.
-
-* failfirst: breaks any error_check loop that is examining
- the error queue.
-
-* message: the message that will be used in the __repr__ of the exception.
-
-* usermessage: the message that will be passed to user in ErrorDialogs
- in Qt-land.
-
-TODO:
-
-* EIPClientError:
- Should inherit from LeapException
-
-* gettext / i18n for user messages.
-
-"""
-from leap.base.exceptions import LeapException
-from leap.util.translations import translate
-
-
-# This should inherit from LeapException
-class EIPClientError(Exception):
- """
- base EIPClient exception
- """
- critical = False
- failfirst = False
- warning = False
-
-
-class CriticalError(EIPClientError):
- """
- we cannot do anything about it, sorry
- """
- critical = True
- failfirst = True
-
-
-class Warning(EIPClientError):
- """
- just that, warnings
- """
- warning = True
-
-
-class EIPNoPolkitAuthAgentAvailable(CriticalError):
- message = "No polkit authentication agent could be found"
- usermessage = translate(
- "EIPErrors",
- "We could not find any authentication "
- "agent in your system.<br/>"
- "Make sure you have "
- "<b>polkit-gnome-authentication-agent-1</b> "
- "running and try again.")
-
-
-class EIPNoPkexecAvailable(Warning):
- message = "No pkexec binary found"
- usermessage = translate(
- "EIPErrors",
- "We could not find <b>pkexec</b> in your "
- "system.<br/> Do you want to try "
- "<b>setuid workaround</b>? "
- "(<i>DOES NOTHING YET</i>)")
- failfirst = True
-
-
-class EIPNoCommandError(EIPClientError):
- message = "no suitable openvpn command found"
- usermessage = translate(
- "EIPErrors",
- "No suitable openvpn command found. "
- "<br/>(Might be a permissions problem)")
-
-
-class EIPBadCertError(Warning):
- # XXX this should be critical and fail close
- message = "cert verification failed"
- usermessage = translate(
- "EIPErrors",
- "there is a problem with provider certificate")
-
-
-class LeapBadConfigFetchedError(Warning):
- message = "provider sent a malformed json file"
- usermessage = translate(
- "EIPErrors",
- "an error occurred during configuratio of leap services")
-
-
-class OpenVPNAlreadyRunning(CriticalError):
- message = "Another OpenVPN Process is already running."
- usermessage = translate(
- "EIPErrors",
- "Another OpenVPN Process has been detected. "
- "Please close it before starting leap-client")
-
-
-class HttpsNotSupported(LeapException):
- message = "connection refused while accessing via https"
- usermessage = translate(
- "EIPErrors",
- "Server does not allow secure connections")
-
-
-class HttpsBadCertError(LeapException):
- message = "verification error on cert"
- usermessage = translate(
- "EIPErrors",
- "Server certificate could not be verified")
-
-#
-# errors still needing some love
-#
-
-
-class EIPInitNoKeyFileError(CriticalError):
- message = "No vpn keys found in the expected path"
- usermessage = translate(
- "EIPErrors",
- "We could not find your eip certs in the expected path")
-
-
-class EIPInitBadKeyFilePermError(Warning):
- # I don't know if we should be telling user or not,
- # we try to fix permissions and should only re-raise
- # if permission check failed.
- pass
-
-
-class EIPInitNoProviderError(EIPClientError):
- pass
-
-
-class EIPInitBadProviderError(EIPClientError):
- pass
-
-
-class EIPConfigurationError(EIPClientError):
- pass
-
-#
-# Errors that probably we don't need anymore
-# chase down for them and check.
-#
-
-
-class MissingSocketError(Exception):
- pass
-
-
-class ConnectionRefusedError(Exception):
- pass
-
-
-class EIPMissingDefaultProvider(Exception):
- pass
diff --git a/src/leap/eip/openvpnconnection.py b/src/leap/eip/openvpnconnection.py
deleted file mode 100644
index 455735c8..00000000
--- a/src/leap/eip/openvpnconnection.py
+++ /dev/null
@@ -1,410 +0,0 @@
-"""
-OpenVPN Connection
-"""
-from __future__ import (print_function)
-from functools import partial
-import logging
-import os
-import psutil
-import shutil
-import select
-import socket
-from time import sleep
-
-logger = logging.getLogger(name=__name__)
-
-from leap.base.connection import Connection
-from leap.base.constants import OPENVPN_BIN
-from leap.util.coroutines import spawn_and_watch_process
-from leap.util.misc import get_openvpn_pids
-
-from leap.eip.udstelnet import UDSTelnet
-from leap.eip import config as eip_config
-from leap.eip import exceptions as eip_exceptions
-
-
-class OpenVPNManagement(object):
-
- # TODO explain a little bit how management interface works
- # and our telnet interface with support for unix sockets.
-
- """
- for more information, read openvpn management notes.
- zcat `dpkg -L openvpn | grep management`
- """
-
- def _connect_to_management(self):
- """
- Connect to openvpn management interface
- """
- if hasattr(self, 'tn'):
- self._close_management_socket()
- self.tn = UDSTelnet(self.host, self.port)
-
- # XXX make password optional
- # specially for win. 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)
- if self.tn:
- self._seek_to_eof()
- return True
-
- def _close_management_socket(self, announce=True):
- """
- Close connection to openvpn management interface
- """
- logger.debug('closing socket')
- if announce:
- self.tn.write("quit\n")
- self.tn.read_all()
- self.tn.get_socket().close()
- del self.tn
-
- def _seek_to_eof(self):
- """
- Read as much as available. Position seek pointer to end of stream
- """
- try:
- b = self.tn.read_eager()
- except EOFError:
- logger.debug("Could not read from socket. Assuming it died.")
- return
- while b:
- try:
- b = self.tn.read_eager()
- except EOFError:
- logger.debug("Could not read from socket. Assuming it died.")
-
- def _send_command(self, cmd):
- """
- Send a command to openvpn and return response as list
- """
- if not self.connected():
- try:
- self._connect_to_management()
- except eip_exceptions.MissingSocketError:
- #logger.warning('missing management socket')
- return []
- try:
- if hasattr(self, 'tn'):
- self.tn.write(cmd + "\n")
- except socket.error:
- logger.error('socket error')
- self._close_management_socket(announce=False)
- return []
- try:
- 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 []
- except socket.error as exc:
- logger.debug('socket error: %s' % exc.message)
- except select.error as exc:
- logger.debug('select error: %s' % exc.message)
-
- 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
-
- #
- # random maybe useful vpn commands
- #
-
- def pid(self):
- #XXX broken
- return self._send_short_command("pid")
-
-
-class OpenVPNConnection(Connection, OpenVPNManagement):
- """
- All related to invocation
- of the openvpn binary.
- It's extended by EIPConnection.
- """
-
- # XXX Inheriting from Connection was an early design idea
- # but currently that's an empty class.
- # We can get rid of that if we don't use it for sharing
- # state with other leap modules.
-
- def __init__(self,
- watcher_cb=None,
- debug=False,
- host=None,
- port="unix",
- password=None,
- *args, **kwargs):
- """
- :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 watcher_cb: function
- :type signal_map: dict
- """
- #XXX FIXME
- #change watcher_cb to line_observer
- # XXX if not host: raise ImproperlyConfigured
-
- logger.debug('init openvpn connection')
- self.debug = debug
- self.ovpn_verbosity = kwargs.get('ovpn_verbosity', None)
-
- 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.command = None
- self.args = None
-
- # XXX get autostart from config
- self.autostart = True
-
- # management interface init
- self.host = host
- if isinstance(port, str) and port.isdigit():
- port = int(port)
- elif port == "unix":
- port = "unix"
- else:
- port = None
- self.port = port
- self.password = password
-
- def run_openvpn_checks(self):
- """
- runs check needed before launching
- openvpn subprocess. will raise if errors found.
- """
- logger.debug('running openvpn checks')
- # XXX I think that "check_if_running" should be called
- # from try openvpn connection instead. -- kali.
- # let's prepare tests for that before changing it...
- self._check_if_running_instance()
- self._set_ovpn_command()
- self._check_vpn_keys()
-
- def try_openvpn_connection(self):
- """
- attempts to connect
- """
- # XXX should make public method
- if self.command is None:
- raise eip_exceptions.EIPNoCommandError
- if self.subp is not None:
- logger.debug('cowardly refusing to launch subprocess again')
- # XXX this is not returning ???!!
- # FIXME -- so it's calling it all the same!!
-
- self._launch_openvpn()
-
- def connected(self):
- """
- Returns True if connected
- rtype: bool
- """
- # XXX make a property
- return hasattr(self, 'tn')
-
- def terminate_openvpn_connection(self, shutdown=False):
- """
- terminates openvpn child subprocess
- """
- if self.subp:
- try:
- self._stop_openvpn()
- except eip_exceptions.ConnectionRefusedError:
- logger.warning(
- 'unable to send sigterm signal to openvpn: '
- 'connection refused.')
-
- # XXX kali --
- # XXX review-me
- # I think this will block if child process
- # does not return.
- # Maybe we can .poll() for a given
- # interval and exit in any case.
-
- RETCODE = self.subp.wait()
- if RETCODE:
- logger.error(
- 'cannot terminate subprocess! Retcode %s'
- '(We might have left openvpn running)' % RETCODE)
-
- if shutdown:
- self._cleanup_tempfiles()
-
- def _cleanup_tempfiles(self):
- """
- remove all temporal files
- we might have left behind
- """
- # if self.port is 'unix', we have
- # created a temporal socket path that, under
- # normal circumstances, we should be able to
- # delete
-
- if self.port == "unix":
- logger.debug('cleaning socket file temp folder')
-
- tempfolder = os.path.split(self.host)[0]
- if os.path.isdir(tempfolder):
- try:
- shutil.rmtree(tempfolder)
- except OSError:
- logger.error('could not delete tmpfolder %s' % tempfolder)
-
- # checks
-
- def _check_if_running_instance(self):
- """
- check if openvpn is already running
- """
- openvpn_pids = get_openvpn_pids()
- if openvpn_pids:
- logger.debug('an openvpn instance is already running.')
- logger.debug('attempting to stop openvpn instance.')
- if not self._stop_openvpn():
- raise eip_exceptions.OpenVPNAlreadyRunning
- return
- else:
- logger.debug('no openvpn instance found.')
-
- def _set_ovpn_command(self):
- try:
- command, args = eip_config.build_ovpn_command(
- provider=self.provider,
- debug=self.debug,
- socket_path=self.host,
- ovpn_verbosity=self.ovpn_verbosity)
- except eip_exceptions.EIPNoPolkitAuthAgentAvailable:
- command = args = None
- raise
- except eip_exceptions.EIPNoPkexecAvailable:
- command = args = None
- raise
-
- # XXX if not command, signal error.
- self.command = command
- self.args = args
-
- def _check_vpn_keys(self):
- """
- checks for correct permissions on vpn keys
- """
- try:
- eip_config.check_vpn_keys(provider=self.provider)
- except eip_exceptions.EIPInitBadKeyFilePermError:
- logger.error('Bad VPN Keys permission!')
- # do nothing now
- # and raise the rest ...
-
- # starting and stopping openvpn subprocess
-
- def _launch_openvpn(self):
- """
- invocation of openvpn binaries in a subprocess.
- """
- #XXX TODO:
- #deprecate watcher_cb,
- #use _only_ signal_maps instead
-
- #logger.debug('_launch_openvpn called')
- if self.watcher_cb is not None:
- linewrite_callback = self.watcher_cb
- else:
- #XXX get logger instead
- linewrite_callback = lambda line: logger.debug(
- 'watcher: %s' % line)
-
- # the partial is not
- # being applied now because we're not observing the process
- # stdout like we did in the early stages. but I leave it
- # here since it will be handy for observing patterns in the
- # thru-the-manager updates (with regex)
- observers = (linewrite_callback,
- partial(lambda con_status,
- line: linewrite_callback, self.status))
- subp, watcher = spawn_and_watch_process(
- self.command,
- self.args,
- observers=observers)
- self.subp = subp
- self.watcher = watcher
-
- def _stop_openvpn(self):
- """
- stop openvpn process
- by sending SIGTERM to the management
- interface
- """
- # XXX method a bit too long, split
- logger.debug("atempting to terminate openvpn process...")
- if self.connected():
- try:
- self._send_command("signal SIGTERM\n")
- sleep(1)
- if not self.subp: # XXX ???
- return True
- except socket.error:
- logger.warning('management socket died')
- return
-
- #shutting openvpn failured
- #try patching in old openvpn host and trying again
- # XXX could be more than one!
- process = self._get_openvpn_process()
- if process:
- logger.debug('process: %s' % process.name)
- cmdline = process.cmdline
-
- manag_flag = "--management"
- if isinstance(cmdline, list) and manag_flag in cmdline:
- _index = cmdline.index(manag_flag)
- self.host = cmdline[_index + 1]
- self._send_command("signal SIGTERM\n")
-
- #make sure the process was terminated
- process = self._get_openvpn_process()
- if not process:
- logger.debug("Existing OpenVPN Process Terminated")
- return True
- else:
- logger.error("Unable to terminate existing OpenVPN Process.")
- return False
-
- return True
-
- def _get_openvpn_process(self):
- for process in psutil.process_iter():
- if OPENVPN_BIN in process.name:
- return process
- return None
-
- def get_log(self, lines=1):
- log = self._send_command("log %s" % lines)
- return log
diff --git a/src/leap/eip/specs.py b/src/leap/eip/specs.py
deleted file mode 100644
index c41fd29b..00000000
--- a/src/leap/eip/specs.py
+++ /dev/null
@@ -1,136 +0,0 @@
-from __future__ import (unicode_literals)
-import os
-
-from leap import __branding
-from leap.base import config as baseconfig
-
-# XXX move provider stuff to base config
-
-PROVIDER_CA_CERT = __branding.get(
- 'provider_ca_file',
- 'cacert.pem')
-
-provider_ca_path = lambda domain: str(os.path.join(
- #baseconfig.get_default_provider_path(),
- baseconfig.get_provider_path(domain),
- 'keys', 'ca',
- 'cacert.pem'
-)) if domain else None
-
-default_provider_ca_path = lambda: str(os.path.join(
- baseconfig.get_default_provider_path(),
- 'keys', 'ca',
- PROVIDER_CA_CERT
-))
-
-PROVIDER_DOMAIN = __branding.get('provider_domain', 'testprovider.example.org')
-
-
-client_cert_path = lambda domain: unicode(os.path.join(
- baseconfig.get_provider_path(domain),
- 'keys', 'client',
- 'openvpn.pem'
-)) if domain else None
-
-default_client_cert_path = lambda: unicode(os.path.join(
- baseconfig.get_default_provider_path(),
- 'keys', 'client',
- 'openvpn.pem'
-))
-
-eipconfig_spec = {
- 'description': 'sample eipconfig',
- 'type': 'object',
- 'properties': {
- 'provider': {
- 'type': unicode,
- 'default': u"%s" % PROVIDER_DOMAIN,
- 'required': True,
- },
- 'transport': {
- 'type': unicode,
- 'default': u"openvpn",
- },
- 'openvpn_protocol': {
- 'type': unicode,
- 'default': u"tcp"
- },
- 'openvpn_port': {
- 'type': int,
- 'default': 80
- },
- 'openvpn_ca_certificate': {
- 'type': unicode, # path
- 'default': default_provider_ca_path
- },
- 'openvpn_client_certificate': {
- 'type': unicode, # path
- 'default': default_client_cert_path
- },
- 'connect_on_login': {
- 'type': bool,
- 'default': True
- },
- 'block_cleartext_traffic': {
- 'type': bool,
- 'default': True
- },
- 'primary_gateway': {
- 'type': unicode,
- 'default': u"location_unknown",
- #'required': True
- },
- 'secondary_gateway': {
- 'type': unicode,
- 'default': u"location_unknown2"
- },
- 'management_password': {
- 'type': unicode
- }
- }
-}
-
-eipservice_config_spec = {
- 'description': 'sample eip service config',
- 'type': 'object',
- 'properties': {
- 'serial': {
- 'type': int,
- 'required': True,
- 'default': 1
- },
- 'version': {
- 'type': int,
- 'required': True,
- 'default': 1
- },
- 'clusters': {
- 'type': list,
- 'default': [
- {"label": {
- "en": "Location Unknown"},
- "name": "location_unknown"}]
- },
- 'gateways': {
- 'type': list,
- 'default': [
- {"capabilities": {
- "adblock": True,
- "filter_dns": True,
- "ports": ["80", "53", "443", "1194"],
- "protocols": ["udp", "tcp"],
- "transport": ["openvpn"],
- "user_ips": False},
- "cluster": "location_unknown",
- "host": "location.example.org",
- "ip_address": "127.0.0.1"}]
- },
- 'openvpn_configuration': {
- 'type': dict,
- 'default': {
- "auth": None,
- "cipher": None,
- "tls-cipher": None}
- }
- }
-}
diff --git a/src/leap/eip/tests/__init__.py b/src/leap/eip/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/eip/tests/__init__.py
+++ /dev/null
diff --git a/src/leap/eip/tests/data.py b/src/leap/eip/tests/data.py
deleted file mode 100644
index a7fe1853..00000000
--- a/src/leap/eip/tests/data.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from __future__ import unicode_literals
-import os
-
-#from leap import __branding
-
-# sample data used in tests
-
-#PROVIDER = __branding.get('provider_domain')
-PROVIDER = "testprovider.example.org"
-
-EIP_SAMPLE_CONFIG = {
- "provider": "%s" % PROVIDER,
- "transport": "openvpn",
- "openvpn_protocol": "tcp",
- "openvpn_port": 80,
- "openvpn_ca_certificate": os.path.expanduser(
- "~/.config/leap/providers/"
- "%s/"
- "keys/ca/cacert.pem" % PROVIDER),
- "openvpn_client_certificate": os.path.expanduser(
- "~/.config/leap/providers/"
- "%s/"
- "keys/client/openvpn.pem" % PROVIDER),
- "connect_on_login": True,
- "block_cleartext_traffic": True,
- "primary_gateway": "location_unknown",
- "secondary_gateway": "location_unknown2",
- #"management_password": "oph7Que1othahwiech6J"
-}
-
-EIP_SAMPLE_SERVICE = {
- "serial": 1,
- "version": 1,
- "clusters": [
- {"label": {
- "en": "Location Unknown"},
- "name": "location_unknown"}
- ],
- "gateways": [
- {"capabilities": {
- "adblock": True,
- "filter_dns": True,
- "ports": ["80", "53", "443", "1194"],
- "protocols": ["udp", "tcp"],
- "transport": ["openvpn"],
- "user_ips": False},
- "cluster": "location_unknown",
- "host": "location.example.org",
- "ip_address": "192.0.43.10"}
- ]
-}
diff --git a/src/leap/eip/tests/test_checks.py b/src/leap/eip/tests/test_checks.py
deleted file mode 100644
index ab11037a..00000000
--- a/src/leap/eip/tests/test_checks.py
+++ /dev/null
@@ -1,373 +0,0 @@
-from BaseHTTPServer import BaseHTTPRequestHandler
-import copy
-import json
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-import os
-import time
-import urlparse
-
-from mock import (patch, Mock)
-
-import jsonschema
-#import ping
-import requests
-
-from leap.base import config as baseconfig
-from leap.base.constants import (DEFAULT_PROVIDER_DEFINITION,
- DEFINITION_EXPECTED_PATH)
-from leap.eip import checks as eipchecks
-from leap.eip import specs as eipspecs
-from leap.eip import exceptions as eipexceptions
-from leap.eip.tests import data as testdata
-from leap.testing.basetest import BaseLeapTest
-from leap.testing.https_server import BaseHTTPSServerTestCase
-from leap.testing.https_server import where as where_cert
-from leap.util.fileutil import mkdir_f
-
-
-class NoLogRequestHandler:
- def log_message(self, *args):
- # don't write log msg to stderr
- pass
-
- def read(self, n=None):
- return ''
-
-
-class EIPCheckTest(BaseLeapTest):
-
- __name__ = "eip_check_tests"
- provider = "testprovider.example.org"
- maxDiff = None
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- # test methods are there, and can be called from run_all
-
- def test_checker_should_implement_check_methods(self):
- checker = eipchecks.EIPConfigChecker(domain=self.provider)
-
- self.assertTrue(hasattr(checker, "check_default_eipconfig"),
- "missing meth")
- self.assertTrue(hasattr(checker, "check_is_there_default_provider"),
- "missing meth")
- self.assertTrue(hasattr(checker, "fetch_definition"), "missing meth")
- self.assertTrue(hasattr(checker, "fetch_eip_service_config"),
- "missing meth")
- self.assertTrue(hasattr(checker, "check_complete_eip_config"),
- "missing meth")
-
- def test_checker_should_actually_call_all_tests(self):
- checker = eipchecks.EIPConfigChecker(domain=self.provider)
-
- mc = Mock()
- checker.run_all(checker=mc)
- self.assertTrue(mc.check_default_eipconfig.called, "not called")
- self.assertTrue(mc.check_is_there_default_provider.called,
- "not called")
- self.assertTrue(mc.fetch_definition.called,
- "not called")
- self.assertTrue(mc.fetch_eip_service_config.called,
- "not called")
- self.assertTrue(mc.check_complete_eip_config.called,
- "not called")
-
- # test individual check methods
-
- def test_check_default_eipconfig(self):
- checker = eipchecks.EIPConfigChecker(domain=self.provider)
- # no eip config (empty home)
- eipconfig_path = checker.eipconfig.filename
- self.assertFalse(os.path.isfile(eipconfig_path))
- checker.check_default_eipconfig()
- # we've written one, so it should be there.
- self.assertTrue(os.path.isfile(eipconfig_path))
- with open(eipconfig_path, 'rb') as fp:
- deserialized = json.load(fp)
-
- # force re-evaluation of the paths
- # small workaround for evaluating home dirs correctly
- EIP_SAMPLE_CONFIG = copy.copy(testdata.EIP_SAMPLE_CONFIG)
- EIP_SAMPLE_CONFIG['openvpn_client_certificate'] = \
- eipspecs.client_cert_path(self.provider)
- EIP_SAMPLE_CONFIG['openvpn_ca_certificate'] = \
- eipspecs.provider_ca_path(self.provider)
- self.assertEqual(deserialized, EIP_SAMPLE_CONFIG)
-
- # TODO: shold ALSO run validation methods.
-
- def test_check_is_there_default_provider(self):
- checker = eipchecks.EIPConfigChecker(domain=self.provider)
- # we do dump a sample eip config, but lacking a
- # default provider entry.
- # This error will be possible catched in a different
- # place, when JSONConfig does validation of required fields.
-
- # passing direct config
- with self.assertRaises(eipexceptions.EIPMissingDefaultProvider):
- checker.check_is_there_default_provider(config={})
-
- # ok. now, messing with real files...
- # blank out default_provider
- sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG)
- sampleconfig['provider'] = None
- eipcfg_path = checker.eipconfig.filename
- mkdir_f(eipcfg_path)
- with open(eipcfg_path, 'w') as fp:
- json.dump(sampleconfig, fp)
- #with self.assertRaises(eipexceptions.EIPMissingDefaultProvider):
- # XXX we should catch this as one of our errors, but do not
- # see how to do it quickly.
- with self.assertRaises(jsonschema.ValidationError):
- #import ipdb;ipdb.set_trace()
- checker.eipconfig.load(fromfile=eipcfg_path)
- checker.check_is_there_default_provider()
-
- sampleconfig = testdata.EIP_SAMPLE_CONFIG
- #eipcfg_path = checker._get_default_eipconfig_path()
- with open(eipcfg_path, 'w') as fp:
- json.dump(sampleconfig, fp)
- checker.eipconfig.load()
- self.assertTrue(checker.check_is_there_default_provider())
-
- def test_fetch_definition(self):
- with patch.object(requests, "get") as mocked_get:
- mocked_get.return_value.status_code = 200
- mocked_get.return_value.headers = {
- 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"}
- mocked_get.return_value.json = DEFAULT_PROVIDER_DEFINITION
- checker = eipchecks.EIPConfigChecker(fetcher=requests)
- sampleconfig = testdata.EIP_SAMPLE_CONFIG
- checker.fetch_definition(config=sampleconfig)
-
- fn = os.path.join(baseconfig.get_default_provider_path(),
- DEFINITION_EXPECTED_PATH)
- with open(fn, 'r') as fp:
- deserialized = json.load(fp)
- self.assertEqual(DEFAULT_PROVIDER_DEFINITION, deserialized)
-
- # XXX TODO check for ConnectionError, HTTPError, InvalidUrl
- # (and proper EIPExceptions are raised).
- # Look at base.test_config.
-
- def test_fetch_eip_service_config(self):
- with patch.object(requests, "get") as mocked_get:
- mocked_get.return_value.status_code = 200
- mocked_get.return_value.headers = {
- 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"}
- mocked_get.return_value.json = testdata.EIP_SAMPLE_SERVICE
- checker = eipchecks.EIPConfigChecker(fetcher=requests)
- sampleconfig = testdata.EIP_SAMPLE_CONFIG
- checker.fetch_eip_service_config(config=sampleconfig)
-
- def test_check_complete_eip_config(self):
- checker = eipchecks.EIPConfigChecker()
- with self.assertRaises(eipexceptions.EIPConfigurationError):
- sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG)
- sampleconfig['provider'] = None
- checker.check_complete_eip_config(config=sampleconfig)
- with self.assertRaises(eipexceptions.EIPConfigurationError):
- sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG)
- del sampleconfig['provider']
- checker.check_complete_eip_config(config=sampleconfig)
-
- # normal case
- sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG)
- checker.check_complete_eip_config(config=sampleconfig)
-
-
-class ProviderCertCheckerTest(BaseLeapTest):
-
- __name__ = "provider_cert_checker_tests"
- provider = "testprovider.example.org"
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- # test methods are there, and can be called from run_all
-
- def test_checker_should_implement_check_methods(self):
- checker = eipchecks.ProviderCertChecker()
-
- # For MVS+
- self.assertTrue(hasattr(checker, "download_ca_cert"),
- "missing meth")
- self.assertTrue(hasattr(checker, "download_ca_signature"),
- "missing meth")
- self.assertTrue(hasattr(checker, "get_ca_signatures"), "missing meth")
- self.assertTrue(hasattr(checker, "is_there_trust_path"),
- "missing meth")
-
- # For MVS
- self.assertTrue(hasattr(checker, "is_there_provider_ca"),
- "missing meth")
- self.assertTrue(hasattr(checker, "is_https_working"), "missing meth")
- self.assertTrue(hasattr(checker, "check_new_cert_needed"),
- "missing meth")
-
- def test_checker_should_actually_call_all_tests(self):
- checker = eipchecks.ProviderCertChecker()
-
- mc = Mock()
- checker.run_all(checker=mc)
- # XXX MVS+
- #self.assertTrue(mc.download_ca_cert.called, "not called")
- #self.assertTrue(mc.download_ca_signature.called, "not called")
- #self.assertTrue(mc.get_ca_signatures.called, "not called")
- #self.assertTrue(mc.is_there_trust_path.called, "not called")
-
- # For MVS
- self.assertTrue(mc.is_there_provider_ca.called, "not called")
- self.assertTrue(mc.is_https_working.called,
- "not called")
- self.assertTrue(mc.check_new_cert_needed.called,
- "not called")
-
- # test individual check methods
-
- @unittest.skip
- def test_is_there_provider_ca(self):
- # XXX commenting out this test.
- # With the generic client this does not make sense,
- # we should dump one there.
- # or test conductor logic.
- checker = eipchecks.ProviderCertChecker()
- self.assertTrue(
- checker.is_there_provider_ca())
-
-
-class ProviderCertCheckerHTTPSTests(BaseHTTPSServerTestCase, BaseLeapTest):
- provider = "testprovider.example.org"
-
- class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
- responses = {
- '/': ['OK', ''],
- '/client.cert': [
- # XXX get sample cert
- '-----BEGIN CERTIFICATE-----',
- '-----END CERTIFICATE-----'],
- '/badclient.cert': [
- 'BADCERT']}
-
- def do_GET(self):
- path = urlparse.urlparse(self.path)
- message = '\n'.join(self.responses.get(
- path.path, None))
- self.send_response(200)
- self.end_headers()
- self.wfile.write(message)
-
- def test_is_https_working(self):
- fetcher = requests
- uri = "https://%s/" % (self.get_server())
- # bare requests call. this should just pass (if there is
- # an https service there).
- fetcher.get(uri, verify=False)
- checker = eipchecks.ProviderCertChecker(fetcher=fetcher)
- self.assertTrue(checker.is_https_working(uri=uri, verify=False))
-
- # for local debugs, when in doubt
- #self.assertTrue(checker.is_https_working(uri="https://github.com",
- #verify=True))
-
- # for the two checks below, I know they fail because no ca
- # cert is passed to them, and I know that's the error that
- # requests return with our implementation.
- # We're receiving this because our
- # server is dying prematurely when the handshake is interrupted on the
- # client side.
- # Since we have access to the server, we could check that
- # the error raised has been:
- # SSL23_READ_BYTES: alert bad certificate
- with self.assertRaises(requests.exceptions.SSLError) as exc:
- fetcher.get(uri, verify=True)
- self.assertTrue(
- "SSL23_GET_SERVER_HELLO:unknown protocol" in exc.message)
-
- # XXX FIXME! Uncomment after #638 is done
- #with self.assertRaises(eipexceptions.EIPBadCertError) as exc:
- #checker.is_https_working(uri=uri, verify=True)
- #self.assertTrue(
- #"cert verification failed" in exc.message)
-
- # get cacert from testing.https_server
- cacert = where_cert('cacert.pem')
- fetcher.get(uri, verify=cacert)
- self.assertTrue(checker.is_https_working(uri=uri, verify=cacert))
-
- # same, but get cacert from leap.custom
- # XXX TODO!
-
- @unittest.skip
- def test_download_new_client_cert(self):
- # FIXME
- # Magick srp decorator broken right now...
- # Have to mock the decorator and inject something that
- # can bypass the authentication
-
- uri = "https://%s/client.cert" % (self.get_server())
- cacert = where_cert('cacert.pem')
- checker = eipchecks.ProviderCertChecker(domain=self.provider)
- credentials = "testuser", "testpassword"
- self.assertTrue(checker.download_new_client_cert(
- credentials=credentials, uri=uri, verify=cacert))
-
- # now download a malformed cert
- uri = "https://%s/badclient.cert" % (self.get_server())
- cacert = where_cert('cacert.pem')
- checker = eipchecks.ProviderCertChecker()
- with self.assertRaises(ValueError):
- self.assertTrue(checker.download_new_client_cert(
- credentials=credentials, uri=uri, verify=cacert))
-
- # did we write cert to its path?
- clientcertfile = eipspecs.client_cert_path()
- self.assertTrue(os.path.isfile(clientcertfile))
- certfile = eipspecs.client_cert_path()
- with open(certfile, 'r') as cf:
- certcontent = cf.read()
- self.assertEqual(certcontent,
- '\n'.join(
- self.request_handler.responses['/client.cert']))
- os.remove(clientcertfile)
-
- def test_is_cert_valid(self):
- checker = eipchecks.ProviderCertChecker()
- # TODO: better exception catching
- # should raise eipexceptions.BadClientCertificate, and give reasons
- # on msg.
- with self.assertRaises(Exception) as exc:
- self.assertFalse(checker.is_cert_valid())
- exc.message = "missing cert"
-
- def test_bad_validity_certs(self):
- checker = eipchecks.ProviderCertChecker()
- certfile = where_cert('leaptestscert.pem')
- self.assertFalse(checker.is_cert_not_expired(
- certfile=certfile,
- now=lambda: time.mktime((2038, 1, 1, 1, 1, 1, 1, 1, 1))))
- self.assertFalse(checker.is_cert_not_expired(
- certfile=certfile,
- now=lambda: time.mktime((1970, 1, 1, 1, 1, 1, 1, 1, 1))))
-
- def test_check_new_cert_needed(self):
- # check: missing cert
- checker = eipchecks.ProviderCertChecker(domain=self.provider)
- self.assertTrue(checker.check_new_cert_needed(skip_download=True))
- # TODO check: malformed cert
- # TODO check: expired cert
- # TODO check: pass test server uri instead of skip
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/eip/tests/test_config.py b/src/leap/eip/tests/test_config.py
deleted file mode 100644
index 72ab3c8e..00000000
--- a/src/leap/eip/tests/test_config.py
+++ /dev/null
@@ -1,298 +0,0 @@
-from collections import OrderedDict
-import json
-import os
-import platform
-import stat
-
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-#from leap.base import constants
-#from leap.eip import config as eip_config
-#from leap import __branding as BRANDING
-from leap.eip import config as eipconfig
-from leap.eip.tests.data import EIP_SAMPLE_CONFIG, EIP_SAMPLE_SERVICE
-from leap.testing.basetest import BaseLeapTest
-from leap.util.fileutil import mkdir_p, mkdir_f
-
-_system = platform.system()
-
-#PROVIDER = BRANDING.get('provider_domain')
-#PROVIDER_SHORTNAME = BRANDING.get('short_name')
-
-
-class EIPConfigTest(BaseLeapTest):
-
- __name__ = "eip_config_tests"
- provider = "testprovider.example.org"
-
- maxDiff = None
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- #
- # helpers
- #
-
- def touch_exec(self):
- path = os.path.join(
- self.tempdir, 'bin')
- mkdir_p(path)
- tfile = os.path.join(
- path,
- 'openvpn')
- open(tfile, 'wb').close()
- os.chmod(tfile, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
-
- def write_sample_eipservice(self, vpnciphers=False, extra_vpnopts=None,
- gateways=None):
- conf = eipconfig.EIPServiceConfig()
- mkdir_f(conf.filename)
- if gateways:
- EIP_SAMPLE_SERVICE['gateways'] = gateways
- if vpnciphers:
- openvpnconfig = OrderedDict({
- "auth": "SHA1",
- "cipher": "AES-128-CBC",
- "tls-cipher": "DHE-RSA-AES128-SHA"})
- if extra_vpnopts:
- for k, v in extra_vpnopts.items():
- openvpnconfig[k] = v
- EIP_SAMPLE_SERVICE['openvpn_configuration'] = openvpnconfig
-
- with open(conf.filename, 'w') as fd:
- fd.write(json.dumps(EIP_SAMPLE_SERVICE))
-
- def write_sample_eipconfig(self):
- conf = eipconfig.EIPConfig()
- folder, f = os.path.split(conf.filename)
- if not os.path.isdir(folder):
- mkdir_p(folder)
- with open(conf.filename, 'w') as fd:
- fd.write(json.dumps(EIP_SAMPLE_CONFIG))
-
- def get_expected_openvpn_args(self, with_openvpn_ciphers=False):
- """
- yeah, this is almost as duplicating the
- code for building the command
- """
- args = []
- eipconf = eipconfig.EIPConfig(domain=self.provider)
- eipconf.load()
- eipsconf = eipconfig.EIPServiceConfig(domain=self.provider)
- eipsconf.load()
-
- username = self.get_username()
- groupname = self.get_groupname()
-
- args.append('--client')
- args.append('--dev')
- #does this have to be tap for win??
- args.append('tun')
- args.append('--persist-tun')
- args.append('--persist-key')
- args.append('--remote')
-
- args.append('%s' % eipconfig.get_eip_gateway(
- eipconfig=eipconf,
- eipserviceconfig=eipsconf))
- # XXX get port!?
- args.append('1194')
- # XXX get proto
- args.append('udp')
- args.append('--tls-client')
- args.append('--remote-cert-tls')
- args.append('server')
-
- if with_openvpn_ciphers:
- CIPHERS = [
- "--tls-cipher", "DHE-RSA-AES128-SHA",
- "--cipher", "AES-128-CBC",
- "--auth", "SHA1"]
- for opt in CIPHERS:
- args.append(opt)
-
- args.append('--user')
- args.append(username)
- args.append('--group')
- args.append(groupname)
- args.append('--management-client-user')
- args.append(username)
- args.append('--management-signal')
-
- args.append('--management')
- #XXX hey!
- #get platform switches here!
- args.append('/tmp/test.socket')
- args.append('unix')
-
- args.append('--script-security')
- args.append('2')
-
- if _system == "Linux":
- UPDOWN_SCRIPT = "/etc/leap/resolv-update"
- if os.path.isfile(UPDOWN_SCRIPT):
- args.append('--up')
- args.append('/etc/leap/resolv-update')
- args.append('--down')
- args.append('/etc/leap/resolv-update')
- args.append('--plugin')
- args.append('/usr/lib/openvpn/openvpn-down-root.so')
- args.append("'script_type=down /etc/leap/resolv-update'")
-
- # certs
- # XXX get values from specs?
- args.append('--cert')
- args.append(os.path.join(
- self.home,
- '.config', 'leap', 'providers',
- '%s' % self.provider,
- 'keys', 'client',
- 'openvpn.pem'))
- args.append('--key')
- args.append(os.path.join(
- self.home,
- '.config', 'leap', 'providers',
- '%s' % self.provider,
- 'keys', 'client',
- 'openvpn.pem'))
- args.append('--ca')
- args.append(os.path.join(
- self.home,
- '.config', 'leap', 'providers',
- '%s' % self.provider,
- 'keys', 'ca',
- 'cacert.pem'))
- return args
-
- # build command string
- # these tests are going to have to check
- # many combinations. we should inject some
- # params in the function call, to disable
- # some checks.
-
- def test_get_eip_gateway(self):
- self.write_sample_eipconfig()
- eipconf = eipconfig.EIPConfig(domain=self.provider)
-
- # default eipservice
- self.write_sample_eipservice()
- eipsconf = eipconfig.EIPServiceConfig(domain=self.provider)
-
- gateway = eipconfig.get_eip_gateway(
- eipconfig=eipconf,
- eipserviceconfig=eipsconf)
-
- # in spec is local gateway by default
- self.assertEqual(gateway, '127.0.0.1')
-
- # change eipservice
- # right now we only check that cluster == selected primary gw in
- # eip.json, and pick first matching ip
- eipconf._config.config['primary_gateway'] = "foo_provider"
- newgateways = [{"cluster": "foo_provider",
- "ip_address": "127.0.0.99"}]
- self.write_sample_eipservice(gateways=newgateways)
- eipsconf = eipconfig.EIPServiceConfig(domain=self.provider)
- # load from disk file
- eipsconf.load()
-
- gateway = eipconfig.get_eip_gateway(
- eipconfig=eipconf,
- eipserviceconfig=eipsconf)
- self.assertEqual(gateway, '127.0.0.99')
-
- # change eipservice, several gateways
- # right now we only check that cluster == selected primary gw in
- # eip.json, and pick first matching ip
- eipconf._config.config['primary_gateway'] = "bar_provider"
- newgateways = [{"cluster": "foo_provider",
- "ip_address": "127.0.0.99"},
- {'cluster': "bar_provider",
- "ip_address": "127.0.0.88"}]
- self.write_sample_eipservice(gateways=newgateways)
- eipsconf = eipconfig.EIPServiceConfig(domain=self.provider)
- # load from disk file
- eipsconf.load()
-
- gateway = eipconfig.get_eip_gateway(
- eipconfig=eipconf,
- eipserviceconfig=eipsconf)
- self.assertEqual(gateway, '127.0.0.88')
-
- def test_build_ovpn_command_empty_config(self):
- self.touch_exec()
- self.write_sample_eipservice()
- self.write_sample_eipconfig()
-
- from leap.eip import config as eipconfig
- from leap.util.fileutil import which
- path = os.environ['PATH']
- vpnbin = which('openvpn', path=path)
- #print 'path =', path
- #print 'vpnbin = ', vpnbin
- vpncommand, vpnargs = eipconfig.build_ovpn_command(
- do_pkexec_check=False, vpnbin=vpnbin,
- socket_path="/tmp/test.socket",
- provider=self.provider)
- self.assertEqual(vpncommand, self.home + '/bin/openvpn')
- self.assertEqual(vpnargs, self.get_expected_openvpn_args())
-
- def test_build_ovpn_command_openvpnoptions(self):
- self.touch_exec()
-
- from leap.eip import config as eipconfig
- from leap.util.fileutil import which
- path = os.environ['PATH']
- vpnbin = which('openvpn', path=path)
-
- self.write_sample_eipconfig()
-
- # regular run, everything normal
- self.write_sample_eipservice(vpnciphers=True)
- vpncommand, vpnargs = eipconfig.build_ovpn_command(
- do_pkexec_check=False, vpnbin=vpnbin,
- socket_path="/tmp/test.socket",
- provider=self.provider)
- self.assertEqual(vpncommand, self.home + '/bin/openvpn')
- expected = self.get_expected_openvpn_args(
- with_openvpn_ciphers=True)
- self.assertEqual(vpnargs, expected)
-
- # bad options -- illegal options
- self.write_sample_eipservice(
- vpnciphers=True,
- # WE ONLY ALLOW vpn options in auth, cipher, tls-cipher
- extra_vpnopts={"notallowedconfig": "badvalue"})
- vpncommand, vpnargs = eipconfig.build_ovpn_command(
- do_pkexec_check=False, vpnbin=vpnbin,
- socket_path="/tmp/test.socket",
- provider=self.provider)
- self.assertEqual(vpncommand, self.home + '/bin/openvpn')
- expected = self.get_expected_openvpn_args(
- with_openvpn_ciphers=True)
- self.assertEqual(vpnargs, expected)
-
- # bad options -- illegal chars
- self.write_sample_eipservice(
- vpnciphers=True,
- # WE ONLY ALLOW A-Z09\-
- extra_vpnopts={"cipher": "AES-128-CBC;FOOTHING"})
- vpncommand, vpnargs = eipconfig.build_ovpn_command(
- do_pkexec_check=False, vpnbin=vpnbin,
- socket_path="/tmp/test.socket",
- provider=self.provider)
- self.assertEqual(vpncommand, self.home + '/bin/openvpn')
- expected = self.get_expected_openvpn_args(
- with_openvpn_ciphers=True)
- self.assertEqual(vpnargs, expected)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/eip/tests/test_eipconnection.py b/src/leap/eip/tests/test_eipconnection.py
deleted file mode 100644
index 163f8d45..00000000
--- a/src/leap/eip/tests/test_eipconnection.py
+++ /dev/null
@@ -1,216 +0,0 @@
-import glob
-import logging
-import platform
-#import os
-import shutil
-
-logging.basicConfig()
-logger = logging.getLogger(name=__name__)
-
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-from mock import Mock, patch # MagicMock
-
-from leap.eip.eipconnection import EIPConnection
-from leap.eip.exceptions import ConnectionRefusedError
-from leap.eip import specs as eipspecs
-from leap.testing.basetest import BaseLeapTest
-
-_system = platform.system()
-
-PROVIDER = "testprovider.example.org"
-
-
-class NotImplementedError(Exception):
- pass
-
-
-@patch('OpenVPNConnection._get_or_create_config')
-@patch('OpenVPNConnection._set_ovpn_command')
-class MockedEIPConnection(EIPConnection):
-
- def _set_ovpn_command(self):
- self.command = "mock_command"
- self.args = [1, 2, 3]
-
-
-class EIPConductorTest(BaseLeapTest):
-
- __name__ = "eip_conductor_tests"
- provider = PROVIDER
-
- def setUp(self):
- # XXX there's a conceptual/design
- # mistake here.
- # If we're testing just attrs after init,
- # init shold not be doing so much side effects.
-
- # for instance:
- # We have to TOUCH a keys file because
- # we're triggerig the key checks FROM
- # the constructor. me not like that,
- # key checker should better be called explicitelly.
-
- # XXX change to keys_checker invocation
- # (see config_checker)
-
- keyfiles = (eipspecs.provider_ca_path(domain=self.provider),
- eipspecs.client_cert_path(domain=self.provider))
- for filepath in keyfiles:
- self.touch(filepath)
- self.chmod600(filepath)
-
- # we init the manager with only
- # some methods mocked
- self.manager = Mock(name="openvpnmanager_mock")
- self.con = MockedEIPConnection()
- self.con.provider = self.provider
-
- # XXX watch out. This sometimes is throwing the following error:
- # NoSuchProcess: process no longer exists (pid=6571)
- # because of a bad implementation of _check_if_running_instance
-
- self.con.run_openvpn_checks()
-
- def tearDown(self):
- pass
-
- def doCleanups(self):
- super(BaseLeapTest, self).doCleanups()
- self.cleanupSocketDir()
- del self.con
-
- def cleanupSocketDir(self):
- ptt = ('/tmp/leap-tmp*')
- for tmpdir in glob.glob(ptt):
- shutil.rmtree(tmpdir)
-
- #
- # tests
- #
-
- def test_vpnconnection_defaults(self):
- """
- default attrs as expected
- """
- con = self.con
- self.assertEqual(con.autostart, True)
- # XXX moar!
-
- def test_ovpn_command(self):
- """
- set_ovpn_command called
- """
- self.assertEqual(self.con.command,
- "mock_command")
- self.assertEqual(self.con.args,
- [1, 2, 3])
-
- # config checks
-
- def test_config_checked_called(self):
- # XXX this single test is taking half of the time
- # needed to run tests. (roughly 3 secs for this only)
- # We should modularize and inject Mocks on more places.
-
- oldcon = self.con
- del(self.con)
- config_checker = Mock()
- self.con = MockedEIPConnection(config_checker=config_checker)
- self.assertTrue(config_checker.called)
- self.con.run_checks()
- self.con.config_checker.run_all.assert_called_with(
- skip_download=False)
-
- # XXX test for cert_checker also
- self.con = oldcon
-
- # connect/disconnect calls
-
- def test_disconnect(self):
- """
- disconnect method calls private and changes status
- """
- self.con._disconnect = Mock(
- name="_disconnect")
-
- # first we set status to connected
- self.con.status.set_current(self.con.status.CONNECTED)
- self.assertEqual(self.con.status.current,
- self.con.status.CONNECTED)
-
- # disconnect
- self.con.terminate_openvpn_connection = Mock()
- self.con.disconnect()
- self.con.terminate_openvpn_connection.assert_called_once_with(
- shutdown=False)
- self.con.terminate_openvpn_connection = Mock()
- self.con.disconnect(shutdown=True)
- self.con.terminate_openvpn_connection.assert_called_once_with(
- shutdown=True)
-
- # new status should be disconnected
- # XXX this should evolve and check no errors
- # during disconnection
- self.assertEqual(self.con.status.current,
- self.con.status.DISCONNECTED)
-
- def test_connect(self):
- """
- connect calls _launch_openvpn private
- """
- self.con._launch_openvpn = Mock()
- self.con.connect()
- self.con._launch_openvpn.assert_called_once_with()
-
- # XXX tests breaking here ...
-
- def test_good_poll_connection_state(self):
- """
- """
- #@patch --
- # self.manager.get_connection_state
-
- #XXX review this set of poll_state tests
- #they SHOULD NOT NEED TO MOCK ANYTHING IN THE
- #lower layers!! -- status, vpn_manager..
- #right now we're testing implementation, not
- #behavior!!!
- good_state = ["1345466946", "unknown_state", "ok",
- "192.168.1.1", "192.168.1.100"]
- self.con.get_connection_state = Mock(return_value=good_state)
- self.con.status.set_vpn_state = Mock()
-
- state = self.con.poll_connection_state()
- good_state[1] = "disconnected"
- final_state = tuple(good_state)
- self.con.status.set_vpn_state.assert_called_with("unknown_state")
- self.assertEqual(state, final_state)
-
- # TODO between "good" and "bad" (exception raised) cases,
- # we can still test for malformed states and see that only good
- # states do have a change (and from only the expected transition
- # states).
-
- def test_bad_poll_connection_state(self):
- """
- get connection state raises ConnectionRefusedError
- state is None
- """
- self.con.get_connection_state = Mock(
- side_effect=ConnectionRefusedError('foo!'))
- state = self.con.poll_connection_state()
- self.assertEqual(state, None)
-
-
- # XXX more things to test:
- # - called config routines during initz.
- # - raising proper exceptions with no config
- # - called proper checks on config / permissions
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/eip/tests/test_openvpnconnection.py b/src/leap/eip/tests/test_openvpnconnection.py
deleted file mode 100644
index 95bfb2f0..00000000
--- a/src/leap/eip/tests/test_openvpnconnection.py
+++ /dev/null
@@ -1,161 +0,0 @@
-import logging
-import os
-import platform
-import psutil
-import shutil
-#import socket
-
-logging.basicConfig()
-logger = logging.getLogger(name=__name__)
-
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-from mock import Mock, patch # MagicMock
-
-from leap.eip import config as eipconfig
-from leap.eip import openvpnconnection
-from leap.eip import exceptions as eipexceptions
-from leap.eip.udstelnet import UDSTelnet
-from leap.testing.basetest import BaseLeapTest
-
-_system = platform.system()
-
-
-class NotImplementedError(Exception):
- pass
-
-
-mock_UDSTelnet = Mock(spec=UDSTelnet)
-# XXX cautious!!!
-# this might be fragile right now (counting a global
-# reference of calls I think.
-# investigate this other form instead:
-# http://www.voidspace.org.uk/python/mock/patch.html#start-and-stop
-
-# XXX redo after merge-refactor
-
-
-@patch('openvpnconnection.OpenVPNConnection.connect_to_management')
-class MockedOpenVPNConnection(openvpnconnection.OpenVPNConnection):
- def __init__(self, *args, **kwargs):
- self.mock_UDSTelnet = Mock()
- super(MockedOpenVPNConnection, self).__init__(
- *args, **kwargs)
- self.tn = self.mock_UDSTelnet(self.host, self.port)
-
- def connect_to_management(self):
- #print 'patched connect'
- self.tn = mock_UDSTelnet(self.host, port=self.port)
-
-
-class OpenVPNConnectionTest(BaseLeapTest):
-
- __name__ = "vpnconnection_tests"
-
- def setUp(self):
- # XXX this will have to change for win, host=localhost
- host = eipconfig.get_socket_path()
- self.host = host
- self.manager = MockedOpenVPNConnection(host=host)
-
- def tearDown(self):
- pass
-
- def doCleanups(self):
- super(BaseLeapTest, self).doCleanups()
- self.cleanupSocketDir()
-
- def cleanupSocketDir(self):
- # remove the socket folder.
- # XXX only if posix. in win, host is localhost, so nothing
- # has to be done.
- if self.host:
- folder, fpath = os.path.split(self.host)
- try:
- assert folder.startswith('/tmp/leap-tmp') # safety check
- shutil.rmtree(folder)
- except:
- self.fail("could not remove temp file")
-
- del self.manager
-
- #
- # tests
- #
-
- def test_detect_vpn(self):
- # XXX review, not sure if captured all the logic
- # while fixing. kali.
- openvpn_connection = openvpnconnection.OpenVPNConnection()
-
- with patch.object(psutil, "process_iter") as mocked_psutil:
- mocked_process = Mock()
- mocked_process.name = "openvpn"
- mocked_process.cmdline = ["openvpn", "-foo", "-bar", "-gaaz"]
- mocked_psutil.return_value = [mocked_process]
- with self.assertRaises(eipexceptions.OpenVPNAlreadyRunning):
- openvpn_connection._check_if_running_instance()
-
- openvpn_connection._check_if_running_instance()
-
- @unittest.skipIf(_system == "Windows", "lin/mac only")
- def test_lin_mac_default_init(self):
- """
- check default host for management iface
- """
- self.assertTrue(self.manager.host.startswith('/tmp/leap-tmp'))
- self.assertEqual(self.manager.port, 'unix')
-
- @unittest.skipUnless(_system == "Windows", "win only")
- def test_win_default_init(self):
- """
- check default host for management iface
- """
- # XXX should we make the platform specific switch
- # here or in the vpn command string building?
- self.assertEqual(self.manager.host, 'localhost')
- self.assertEqual(self.manager.port, 7777)
-
- def test_port_types_init(self):
- oldmanager = self.manager
- self.manager = MockedOpenVPNConnection(port="42")
- self.assertEqual(self.manager.port, 42)
- self.manager = MockedOpenVPNConnection()
- self.assertEqual(self.manager.port, "unix")
- self.manager = MockedOpenVPNConnection(port="bad")
- self.assertEqual(self.manager.port, None)
- self.manager = oldmanager
-
- def test_uds_telnet_called_on_connect(self):
- self.manager.connect_to_management()
- mock_UDSTelnet.assert_called_with(
- self.manager.host,
- port=self.manager.port)
-
- @unittest.skip
- def test_connect(self):
- raise NotImplementedError
- # XXX calls close
- # calls UDSTelnet mock.
-
- # XXX
- # tests to write:
- # UDSTelnetTest (for real?)
- # HAVE A LOOK AT CORE TESTS FOR TELNETLIB.
- # very illustrative instead...
-
- # - raise MissingSocket
- # - raise ConnectionRefusedError
- # - test send command
- # - tries connect
- # - ... tries?
- # - ... calls _seek_to_eof
- # - ... read_until --> return value
- # - ...
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/eip/udstelnet.py b/src/leap/eip/udstelnet.py
deleted file mode 100644
index 18e927c2..00000000
--- a/src/leap/eip/udstelnet.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-import socket
-import telnetlib
-
-from leap.eip import exceptions as eip_exceptions
-
-
-class UDSTelnet(telnetlib.Telnet):
- """
- a telnet-alike class, that can listen
- on unix domain sockets
- """
-
- def open(self, host, port=23, 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
- self.host = host
- self.port = port
- self.timeout = timeout
-
- if self.port == "unix":
- # unix sockets spoken
- if not os.path.exists(self.host):
- raise eip_exceptions.MissingSocketError
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- try:
- self.sock.connect(self.host)
- except socket.error:
- raise eip_exceptions.ConnectionRefusedError
- else:
- self.sock = socket.create_connection((host, port), timeout)
diff --git a/src/leap/eip/vpnmanager.py b/src/leap/eip/vpnmanager.py
new file mode 100644
index 00000000..caf7ab76
--- /dev/null
+++ b/src/leap/eip/vpnmanager.py
@@ -0,0 +1,263 @@
+from __future__ import (print_function)
+import logging
+import os
+import socket
+import telnetlib
+import time
+
+logger = logging.getLogger(name=__name__)
+logger.setLevel('DEBUG')
+
+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):
+ logger.debug('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
index 804bfbc1..e69de29b 100644
--- a/src/leap/gui/__init__.py
+++ b/src/leap/gui/__init__.py
@@ -1,11 +0,0 @@
-try:
- import sip
- sip.setapi('QString', 2)
- sip.setapi('QVariant', 2)
-except ValueError:
- pass
-
-import firstrun
-import firstrun.wizard
-
-__all__ = ['firstrun', 'firstrun.wizard']
diff --git a/src/leap/gui/constants.py b/src/leap/gui/constants.py
deleted file mode 100644
index 277f3540..00000000
--- a/src/leap/gui/constants.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import time
-
-APP_LOGO = ':/images/leap-color-small.png'
-
-# bare is the username portion of a JID
-# full includes the "at" and some extra chars
-# that can be allowed for fqdn
-
-BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
-FULL_USERNAME_REGEX = r"^[A-Za-z\d_@.-]+$"
-
-GUI_PAUSE_FOR_USER_SECONDS = 1
-pause_for_user = lambda: time.sleep(GUI_PAUSE_FOR_USER_SECONDS)
diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py
deleted file mode 100644
index 2a523d6a..00000000
--- a/src/leap/gui/firstrun/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-try:
- import sip
- sip.setapi('QString', 2)
- sip.setapi('QVariant', 2)
-except ValueError:
- pass
-
-import intro
-import connect
-import last
-import login
-import mixins
-import providerinfo
-import providerselect
-import providersetup
-import register
-
-__all__ = [
- 'intro',
- 'connect',
- 'last',
- 'login',
- 'mixins',
- 'providerinfo',
- 'providerselect',
- 'providersetup',
- 'register',
- ] # ,'wizard']
diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py
deleted file mode 100644
index ad7bb13a..00000000
--- a/src/leap/gui/firstrun/connect.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""
-Provider Setup Validation Page,
-used in First Run Wizard
-"""
-import logging
-
-from PyQt4 import QtGui
-
-#import requests
-
-from leap.gui.progress import ValidationPage
-from leap.util.web import get_https_domain_and_port
-
-from leap.base import auth
-from leap.gui.constants import APP_LOGO
-
-logger = logging.getLogger(__name__)
-
-
-class ConnectionPage(ValidationPage):
-
- def __init__(self, parent=None):
- super(ConnectionPage, self).__init__(parent)
- self.current_page = "connect"
-
- title = self.tr("Connecting...")
- subtitle = self.tr("Setting up a encrypted "
- "connection with the provider")
-
- self.setTitle(title)
- self.setSubTitle(subtitle)
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- def _do_checks(self, update_signal=None):
- """
- executes actual checks in a separate thread
-
- we initialize the srp protocol register
- and try to register user.
- """
- wizard = self.wizard()
- full_domain = self.field('provider_domain')
- domain, port = get_https_domain_and_port(full_domain)
-
- pconfig = wizard.eipconfigchecker(domain=domain)
- # this should be persisted...
- pconfig.defaultprovider.load()
- pconfig.set_api_domain()
-
- pCertChecker = wizard.providercertchecker(
- domain=domain)
- pCertChecker.set_api_domain(pconfig.apidomain)
-
- ###########################################
- # Set Credentials.
- # username and password are in different fields
- # if they were stored in log_in or sign_up pages.
- from_login = wizard.from_login
-
- unamek_base = 'userName'
- passwk_base = 'userPassword'
- unamek = 'login_%s' % unamek_base if from_login else unamek_base
- passwk = 'login_%s' % passwk_base if from_login else passwk_base
-
- username = self.field(unamek)
- password = self.field(passwk)
- credentials = username, password
-
- yield(("head_sentinel", 0), lambda: None)
-
- ##################################################
- # 1) fetching eip service config
- ##################################################
- def fetcheipconf():
- try:
- pconfig.fetch_eip_service_config()
-
- # XXX get specific exception
- except Exception as exc:
- return self.fail(exc.message)
-
- yield((self.tr("Getting EIP configuration files"), 40),
- fetcheipconf)
-
- ##################################################
- # 2) getting client certificate
- ##################################################
-
- def fetcheipcert():
- try:
- downloaded = pCertChecker.download_new_client_cert(
- credentials=credentials)
- if not downloaded:
- logger.error('Could not download client cert')
- return False
-
- except auth.SRPAuthenticationError as exc:
- return self.fail(self.tr(
- "Authentication error: %s" % exc.message))
-
- except Exception as exc:
- return self.fail(exc.message)
- else:
- return True
-
- yield((self.tr("Getting EIP certificate"), 80),
- fetcheipcert)
-
- ################
- # end !
- ################
- self.set_done()
- yield(("end_sentinel", 100), lambda: None)
-
- def on_checks_validation_ready(self):
- """
- called after _do_checks has finished
- (connected to checker thread finished signal)
- """
- # here we go! :)
- if self.is_done():
- nextbutton = self.wizard().button(QtGui.QWizard.NextButton)
- nextbutton.setFocus()
-
- full_domain = self.field('provider_domain')
- domain, port = get_https_domain_and_port(full_domain)
- _domain = u"%s:%s" % (
- domain, port) if port != 443 else unicode(domain)
- self.run_eip_checks_for_provider_and_connect(_domain)
-
- def run_eip_checks_for_provider_and_connect(self, domain):
- wizard = self.wizard()
- conductor = wizard.conductor
- start_eip_signal = getattr(
- wizard,
- 'start_eipconnection_signal', None)
-
- if conductor:
- conductor.set_provider_domain(domain)
- # we could run some of the checks to be
- # sure everything is in order, but
- # I see no point in doing it, we assume
- # we've gone thru all checks during the wizard.
- #conductor.run_checks()
- #self.conductor = conductor
- #errors = self.eip_error_check()
- #if not errors and start_eip_signal:
- if start_eip_signal:
- start_eip_signal.emit()
-
- else:
- logger.warning(
- "No conductor found. This means that "
- "probably the wizard has been launched "
- "in an stand-alone way.")
-
- self.set_done()
-
- #def eip_error_check(self):
- #"""
- #a version of the main app error checker,
- #but integrated within the connecting page of the wizard.
- #consumes the conductor error queue.
- #pops errors, and add those to the wizard page
- #"""
- # TODO handle errors.
- # We should redirect them to the log viewer
- # with a brief message.
- # XXX move to LAST PAGE instead.
- #logger.debug('eip error check from connecting page')
- #errq = self.conductor.error_queue
-
- #def _do_validation(self):
- #"""
- #called after _do_checks has finished
- #(connected to checker thread finished signal)
- #"""
- #from_login = self.wizard().from_login
- #prevpage = "login" if from_login else "signup"
-
- #wizard = self.wizard()
- #if self.errors:
- #logger.debug('going back with errors')
- #logger.error(self.errors)
- #name, first_error = self.pop_first_error()
- #wizard.set_validation_error(
- #prevpage,
- #first_error)
- #self.go_back()
-
- def nextId(self):
- wizard = self.wizard()
- return wizard.get_page_index('lastpage')
-
- def initializePage(self):
- super(ConnectionPage, self).initializePage()
- self.set_undone()
- cancelbutton = self.wizard().button(QtGui.QWizard.CancelButton)
- cancelbutton.hide()
- self.completeChanged.emit()
-
- wizard = self.wizard()
- eip_statuschange_signal = wizard.eip_statuschange_signal
- if eip_statuschange_signal:
- eip_statuschange_signal.connect(
- lambda status: self.send_status(
- status))
-
- def send_status(self, status):
- wizard = self.wizard()
- wizard.openvpn_status.append(status)
diff --git a/src/leap/gui/firstrun/constants.py b/src/leap/gui/firstrun/constants.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/gui/firstrun/constants.py
+++ /dev/null
diff --git a/src/leap/gui/firstrun/intro.py b/src/leap/gui/firstrun/intro.py
deleted file mode 100644
index b519362f..00000000
--- a/src/leap/gui/firstrun/intro.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-Intro page used in first run wizard
-"""
-
-from PyQt4 import QtGui
-
-from leap.gui.constants import APP_LOGO
-
-
-class IntroPage(QtGui.QWizardPage):
- def __init__(self, parent=None):
- super(IntroPage, self).__init__(parent)
-
- self.setTitle(self.tr("First run wizard"))
-
- #self.setPixmap(
- #QtGui.QWizard.WatermarkPixmap,
- #QtGui.QPixmap(':/images/watermark1.png'))
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- label = QtGui.QLabel(self.tr(
- "Now we will guide you through "
- "some configuration that is needed before you "
- "can connect for the first time.<br><br>"
- "If you ever need to modify these options again, "
- "you can find the wizard in the '<i>Settings</i>' menu from the "
- "main window.<br><br>"
- "Do you want to <b>sign up</b> for a new account, or <b>log "
- "in</b> with an already existing username?<br>"))
- label.setWordWrap(True)
-
- radiobuttonGroup = QtGui.QGroupBox()
-
- self.sign_up = QtGui.QRadioButton(
- self.tr("Sign up for a new account"))
- self.sign_up.setChecked(True)
- self.log_in = QtGui.QRadioButton(
- self.tr("Log In with my credentials"))
-
- radiobLayout = QtGui.QVBoxLayout()
- radiobLayout.addWidget(self.sign_up)
- radiobLayout.addWidget(self.log_in)
- radiobuttonGroup.setLayout(radiobLayout)
-
- layout = QtGui.QVBoxLayout()
- layout.addWidget(label)
- layout.addWidget(radiobuttonGroup)
- self.setLayout(layout)
-
- #self.registerField('is_signup', self.sign_up)
-
- def validatePage(self):
- return True
-
- def nextId(self):
- """
- returns next id
- in a non-linear wizard
- """
- if self.sign_up.isChecked():
- next_ = 'providerselection'
- if self.log_in.isChecked():
- next_ = 'login'
- wizard = self.wizard()
- return wizard.get_page_index(next_)
diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py
deleted file mode 100644
index f3e467db..00000000
--- a/src/leap/gui/firstrun/last.py
+++ /dev/null
@@ -1,119 +0,0 @@
-"""
-Last Page, used in First Run Wizard
-"""
-import logging
-
-from PyQt4 import QtGui
-
-from leap.util.coroutines import coroutine
-from leap.gui.constants import APP_LOGO
-
-logger = logging.getLogger(__name__)
-
-
-class LastPage(QtGui.QWizardPage):
- def __init__(self, parent=None):
- super(LastPage, self).__init__(parent)
-
- self.setTitle(self.tr(
- "Connecting to Encrypted Internet Proxy service..."))
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- #self.setPixmap(
- #QtGui.QWizard.WatermarkPixmap,
- #QtGui.QPixmap(':/images/watermark2.png'))
-
- self.label = QtGui.QLabel()
- self.label.setWordWrap(True)
-
- self.wizard_done = False
-
- # XXX REFACTOR to a Validating Page...
- self.status_line_1 = QtGui.QLabel()
- self.status_line_2 = QtGui.QLabel()
- self.status_line_3 = QtGui.QLabel()
- self.status_line_4 = QtGui.QLabel()
- self.status_line_5 = QtGui.QLabel()
-
- layout = QtGui.QVBoxLayout()
- layout.addWidget(self.label)
-
- # make loop
- layout.addWidget(self.status_line_1)
- layout.addWidget(self.status_line_2)
- layout.addWidget(self.status_line_3)
- layout.addWidget(self.status_line_4)
- layout.addWidget(self.status_line_5)
-
- self.setLayout(layout)
-
- def isComplete(self):
- return self.wizard_done
-
- def set_status_line(self, line, status):
- statusline = getattr(self, 'status_line_%s' % line)
- if statusline:
- statusline.setText(status)
-
- def set_finished_status(self):
- self.setTitle(self.tr('You are now using an encrypted connection!'))
- finishText = self.wizard().buttonText(
- QtGui.QWizard.FinishButton)
- finishText = finishText.replace('&', '')
- self.label.setText(self.tr(
- "Click '<i>%s</i>' to end the wizard and "
- "save your settings." % finishText))
- self.wizard_done = True
- self.completeChanged.emit()
-
- @coroutine
- def eip_status_handler(self):
- # XXX this can be changed to use
- # signals. See progress.py
- logger.debug('logging status in last page')
- self.validation_done = False
- status_count = 1
- try:
- while True:
- status = (yield)
- status_count += 1
- # XXX add to line...
- logger.debug('status --> %s', status)
- self.set_status_line(status_count, status)
- if status == "connected":
- self.set_finished_status()
- self.completeChanged.emit()
- break
- self.completeChanged.emit()
- except GeneratorExit:
- pass
- except StopIteration:
- pass
-
- def initializePage(self):
- super(LastPage, self).initializePage()
- wizard = self.wizard()
- wizard.button(QtGui.QWizard.FinishButton).setDisabled(True)
-
- handler = self.eip_status_handler()
-
- # get statuses done in prev page
- for st in wizard.openvpn_status:
- self.send_status(handler.send, st)
-
- # bind signal for events yet to come
- eip_statuschange_signal = wizard.eip_statuschange_signal
- if eip_statuschange_signal:
- eip_statuschange_signal.connect(
- lambda status: self.send_status(
- handler.send, status))
- self.completeChanged.emit()
-
- def send_status(self, cb, status):
- try:
- cb(status)
- except StopIteration:
- pass
diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py
deleted file mode 100644
index 3707d3ff..00000000
--- a/src/leap/gui/firstrun/login.py
+++ /dev/null
@@ -1,332 +0,0 @@
-"""
-LogIn Page, used inf First Run Wizard
-"""
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-import requests
-
-from leap.base import auth
-from leap.gui.firstrun.mixins import UserFormMixIn
-from leap.gui.progress import InlineValidationPage
-from leap.gui import styles
-
-from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX
-
-
-class LogInPage(InlineValidationPage, UserFormMixIn): # InlineValidationPage
-
- def __init__(self, parent=None):
-
- super(LogInPage, self).__init__(parent)
- self.current_page = "login"
-
- self.setTitle(self.tr("Log In"))
- self.setSubTitle(self.tr("Log in with your credentials"))
- self.current_page = "login"
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- self.setupSteps()
- self.setupUI()
-
- self.do_confirm_next = False
-
- def setupUI(self):
- userNameLabel = QtGui.QLabel(self.tr("User &name:"))
- userNameLineEdit = QtGui.QLineEdit()
- userNameLineEdit.cursorPositionChanged.connect(
- self.reset_validation_status)
- userNameLabel.setBuddy(userNameLineEdit)
-
- # let's add regex validator
- usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX)
- userNameLineEdit.setValidator(
- QtGui.QRegExpValidator(usernameRe, self))
-
- #userNameLineEdit.setPlaceholderText(
- #'username@provider.example.org')
- self.userNameLineEdit = userNameLineEdit
-
- userPasswordLabel = QtGui.QLabel(self.tr("&Password:"))
- self.userPasswordLineEdit = QtGui.QLineEdit()
- self.userPasswordLineEdit.setEchoMode(
- QtGui.QLineEdit.Password)
- userPasswordLabel.setBuddy(self.userPasswordLineEdit)
-
- self.registerField('login_userName*', self.userNameLineEdit)
- self.registerField('login_userPassword*', self.userPasswordLineEdit)
-
- layout = QtGui.QGridLayout()
- layout.setColumnMinimumWidth(0, 20)
-
- validationMsg = QtGui.QLabel("")
- validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)
- self.validationMsg = validationMsg
-
- layout.addWidget(validationMsg, 0, 3)
- layout.addWidget(userNameLabel, 1, 0)
- layout.addWidget(self.userNameLineEdit, 1, 3)
- layout.addWidget(userPasswordLabel, 2, 0)
- layout.addWidget(self.userPasswordLineEdit, 2, 3)
-
- # add validation frame
- self.setupValidationFrame()
- layout.addWidget(self.valFrame, 4, 2, 4, 2)
- self.valFrame.hide()
-
- self.nextText(self.tr("Log in"))
- self.setLayout(layout)
-
- #self.registerField('is_login_wizard')
-
- # actual checks
-
- def _do_checks(self):
-
- full_username = self.userNameLineEdit.text()
- ###########################
- # 0) check user@domain form
- ###########################
-
- def checkusername():
- if full_username.count('@') != 1:
- return self.fail(
- self.tr(
- "Username must be in the username@provider form."))
- else:
- return True
-
- yield(("head_sentinel", 0), checkusername)
-
- username, domain = full_username.split('@')
- password = self.userPasswordLineEdit.text()
-
- # We try a call to an authenticated
- # page here as a mean to catch
- # srp authentication errors while
- wizard = self.wizard()
- eipconfigchecker = wizard.eipconfigchecker(domain=domain)
-
- ########################
- # 1) try name resolution
- ########################
- # show the frame before going on...
- QtCore.QMetaObject.invokeMethod(
- self, "showStepsFrame")
-
- # Able to contact domain?
- # can get definition?
- # two-by-one
- def resolvedomain():
- try:
- eipconfigchecker.fetch_definition(domain=domain)
-
- # we're using requests here for all
- # the possible error cases that it catches.
- except requests.exceptions.ConnectionError as exc:
- return self.fail(exc.message[1])
- except requests.exceptions.HTTPError as exc:
- return self.fail(exc.message)
- except Exception as exc:
- # XXX get catchall error msg
- return self.fail(
- exc.message)
- else:
- return True
-
- yield((self.tr("Resolving domain name"), 20), resolvedomain)
-
- wizard.set_providerconfig(
- eipconfigchecker.defaultprovider.config)
-
- ########################
- # 2) do authentication
- ########################
- credentials = username, password
- pCertChecker = wizard.providercertchecker(
- domain=domain)
-
- def validate_credentials():
- #################
- # FIXME #BUG #638
- verify = False
-
- try:
- pCertChecker.download_new_client_cert(
- credentials=credentials,
- verify=verify)
-
- except auth.SRPAuthenticationError as exc:
- return self.fail(
- self.tr("Authentication error: %s" % exc.message))
-
- except Exception as exc:
- return self.fail(exc.message)
-
- else:
- return True
-
- yield(('Validating credentials', 60), validate_credentials)
-
- self.set_done()
- yield(("end_sentinel", 100), lambda: None)
-
- def green_validation_status(self):
- val = self.validationMsg
- val.setText(self.tr('Credentials validated.'))
- val.setStyleSheet(styles.GreenLineEdit)
-
- def on_checks_validation_ready(self):
- """
- after checks
- """
- if self.is_done():
- self.disableFields()
- self.cleanup_errormsg()
- self.clean_wizard_errors(self.current_page)
- # make the user confirm the transition
- # to next page.
- self.nextText('&Next')
- self.nextFocus()
- self.green_validation_status()
- self.do_confirm_next = True
-
- # ui update
-
- def nextText(self, text):
- self.setButtonText(
- QtGui.QWizard.NextButton, text)
-
- def nextFocus(self):
- self.wizard().button(
- QtGui.QWizard.NextButton).setFocus()
-
- def disableNextButton(self):
- self.wizard().button(
- QtGui.QWizard.NextButton).setDisabled(True)
-
- def onUserNamePositionChanged(self, *args):
- if self.initial_username_sample:
- self.userNameLineEdit.setText('')
- # XXX set regular color
- self.initial_username_sample = None
-
- def onUserNameTextChanged(self, *args):
- if self.initial_username_sample:
- k = args[0][-1]
- self.initial_username_sample = None
- self.userNameLineEdit.setText(k)
-
- def disableFields(self):
- for field in (self.userNameLineEdit,
- self.userPasswordLineEdit):
- field.setDisabled(True)
-
- def populateErrors(self):
- # XXX could move this to ValidationMixin
- # used in providerselect and register too
-
- errors = self.wizard().get_validation_error(
- self.current_page)
- showerr = self.validationMsg.setText
-
- if errors:
- bad_str = getattr(self, 'bad_string', None)
- cur_str = self.userNameLineEdit.text()
-
- if bad_str is None:
- # first time we fall here.
- # save the current bad_string value
- self.bad_string = cur_str
- showerr(errors)
- else:
- # not the first time
- if cur_str == bad_str:
- showerr(errors)
- else:
- self.focused_field = False
- showerr('')
-
- def cleanup_errormsg(self):
- """
- we reset bad_string to None
- should be called before leaving the page
- """
- self.bad_string = None
-
- def paintEvent(self, event):
- """
- we hook our populate errors
- on paintEvent because we need it to catch
- when user enters the page coming from next,
- and initializePage does not cover that case.
- Maybe there's a better event to hook upon.
- """
- super(LogInPage, self).paintEvent(event)
- self.populateErrors()
-
- def set_prevalidation_error(self, error):
- self.prevalidation_error = error
-
- # pagewizard methods
-
- def nextId(self):
- wizard = self.wizard()
- if not wizard:
- return
- if wizard.is_provider_setup is False:
- next_ = 'providersetupvalidation'
- if wizard.is_provider_setup is True:
- # XXX bad name, ok, gonna change that
- next_ = 'signupvalidation'
- return wizard.get_page_index(next_)
-
- def initializePage(self):
- super(LogInPage, self).initializePage()
- username = self.userNameLineEdit
- username.setText('username@provider.example.org')
- username.cursorPositionChanged.connect(
- self.onUserNamePositionChanged)
- username.textChanged.connect(
- self.onUserNameTextChanged)
- self.initial_username_sample = True
- self.validationMsg.setText('')
- self.valFrame.hide()
-
- def reset_validation_status(self):
- """
- empty the validation msg
- and clean the inline validation widget.
- """
- self.validationMsg.setText('')
- self.steps.removeAllSteps()
- self.clearTable()
-
- def validatePage(self):
- """
- if not register done, do checks.
- if done, wait for click.
- """
- self.disableNextButton()
- self.cleanup_errormsg()
- self.clean_wizard_errors(self.current_page)
-
- if self.do_confirm_next:
- full_username = self.userNameLineEdit.text()
- password = self.userPasswordLineEdit.text()
- username, domain = full_username.split('@')
- self.setField('provider_domain', domain)
- self.setField('login_userName', username)
- self.setField('login_userPassword', password)
- self.wizard().from_login = True
-
- return True
-
- if not self.is_done():
- self.reset_validation_status()
- self.do_checks()
-
- return self.is_done()
diff --git a/src/leap/gui/firstrun/mixins.py b/src/leap/gui/firstrun/mixins.py
deleted file mode 100644
index c4731893..00000000
--- a/src/leap/gui/firstrun/mixins.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-mixins used in First Run Wizard
-"""
-
-
-class UserFormMixIn(object):
-
- def reset_validation_status(self):
- """
- empty the validation msg
- """
- self.validationMsg.setText('')
-
- def set_validation_status(self, msg):
- """
- set generic validation status
- """
- self.validationMsg.setText(msg)
diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py
deleted file mode 100644
index cff4caca..00000000
--- a/src/leap/gui/firstrun/providerinfo.py
+++ /dev/null
@@ -1,106 +0,0 @@
-"""
-Provider Info Page, used in First run Wizard
-"""
-import logging
-
-from PyQt4 import QtGui
-
-from leap.gui.constants import APP_LOGO
-from leap.util.translations import translate
-
-logger = logging.getLogger(__name__)
-
-
-class ProviderInfoPage(QtGui.QWizardPage):
-
- def __init__(self, parent=None):
- super(ProviderInfoPage, self).__init__(parent)
-
- self.setTitle(self.tr("Provider Information"))
- self.setSubTitle(self.tr(
- "Services offered by this provider"))
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- self.create_info_panel()
-
- def create_info_panel(self):
- # Use stacked widget instead
- # of reparenting the layout.
-
- infoWidget = QtGui.QStackedWidget()
-
- info = QtGui.QWidget()
- layout = QtGui.QVBoxLayout()
-
- displayName = QtGui.QLabel("")
- description = QtGui.QLabel("")
- enrollment_policy = QtGui.QLabel("")
-
- # XXX set stylesheet...
- # prettify a little bit.
- # bigger fonts and so on...
-
- # We could use a QFrame here
-
- layout.addWidget(displayName)
- layout.addWidget(description)
- layout.addWidget(enrollment_policy)
- layout.addStretch(1)
-
- info.setLayout(layout)
- infoWidget.addWidget(info)
-
- pageLayout = QtGui.QVBoxLayout()
- pageLayout.addWidget(infoWidget)
- self.setLayout(pageLayout)
-
- # add refs to self to allow for
- # updates.
- # Watch out! Have to get rid of these references!
- # this should be better handled with signals !!
- self.displayName = displayName
- self.description = description
- self.description.setWordWrap(True)
- self.enrollment_policy = enrollment_policy
-
- def show_provider_info(self):
-
- # XXX get multilingual objects
- # directly from the config object
-
- lang = "en"
- pconfig = self.wizard().providerconfig
-
- dn = pconfig.get('name')
- display_name = dn[lang] if dn else ''
- domain_name = self.field('provider_domain')
-
- self.displayName.setText(
- "<b>%s</b> https://%s" % (display_name, domain_name))
-
- desc = pconfig.get('description')
-
- #description_text = desc[lang] if desc else ''
- description_text = translate(desc) if desc else ''
-
- self.description.setText(
- "<i>%s</i>" % description_text)
-
- # XXX should translate this...
- enroll = pconfig.get('enrollment_policy')
- if enroll:
- self.enrollment_policy.setText(
- '<b>%s</b>: <em>%s</em>' % (
- self.tr('enrollment policy'),
- enroll))
-
- def nextId(self):
- wizard = self.wizard()
- next_ = "providersetupvalidation"
- return wizard.get_page_index(next_)
-
- def initializePage(self):
- self.show_provider_info()
diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py
deleted file mode 100644
index 917b16fd..00000000
--- a/src/leap/gui/firstrun/providerselect.py
+++ /dev/null
@@ -1,471 +0,0 @@
-"""
-Select Provider Page, used in First Run Wizard
-"""
-import logging
-
-import requests
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap.base import exceptions as baseexceptions
-#from leap.crypto import certs
-from leap.eip import exceptions as eipexceptions
-from leap.gui.progress import InlineValidationPage
-from leap.gui import styles
-from leap.gui.utils import delay
-from leap.util.web import get_https_domain_and_port
-
-from leap.gui.constants import APP_LOGO
-
-logger = logging.getLogger(__name__)
-
-
-class SelectProviderPage(InlineValidationPage):
-
- launchChecks = QtCore.pyqtSignal()
-
- def __init__(self, parent=None, providers=None):
- super(SelectProviderPage, self).__init__(parent)
- self.current_page = 'providerselection'
-
- self.setTitle(self.tr("Enter Provider"))
- self.setSubTitle(self.tr(
- "Please enter the domain of the provider you want "
- "to use for your connection")
- )
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- self.did_cert_check = False
-
- self.done = False
-
- self.setupSteps()
- self.setupUI()
-
- self.launchChecks.connect(
- self.launch_checks)
-
- self.providerNameEdit.editingFinished.connect(
- lambda: self.providerCheckButton.setFocus(True))
-
- def setupUI(self):
- """
- initializes the UI
- """
- providerNameLabel = QtGui.QLabel("h&ttps://")
- # note that we expect the bare domain name
- # we will add the scheme later
- providerNameEdit = QtGui.QLineEdit()
- providerNameEdit.cursorPositionChanged.connect(
- self.reset_validation_status)
- providerNameLabel.setBuddy(providerNameEdit)
-
- # add regex validator
- providerDomainRe = QtCore.QRegExp(r"^[a-z1-9_\-\.]+$")
- providerNameEdit.setValidator(
- QtGui.QRegExpValidator(providerDomainRe, self))
- self.providerNameEdit = providerNameEdit
-
- # Eventually we will seed a list of
- # well known providers here.
-
- #providercombo = QtGui.QComboBox()
- #if providers:
- #for provider in providers:
- #providercombo.addItem(provider)
- #providerNameSelect = providercombo
-
- self.registerField("provider_domain*", self.providerNameEdit)
- #self.registerField('provider_name_index', providerNameSelect)
-
- validationMsg = QtGui.QLabel("")
- validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)
- self.validationMsg = validationMsg
- providerCheckButton = QtGui.QPushButton(self.tr("chec&k!"))
- self.providerCheckButton = providerCheckButton
-
- # cert info
-
- # this is used in the callback
- # for the checkbox changes.
- # tricky, since the first time came
- # from the exception message.
- # should get string from exception too!
- self.bad_cert_status = self.tr(
- "Server certificate could not be verified.")
-
- self.certInfo = QtGui.QLabel("")
- self.certInfo.setWordWrap(True)
- self.certWarning = QtGui.QLabel("")
- self.trustProviderCertCheckBox = QtGui.QCheckBox(
- self.tr("&Trust this provider certificate."))
-
- self.trustProviderCertCheckBox.stateChanged.connect(
- self.onTrustCheckChanged)
- self.providerNameEdit.textChanged.connect(
- self.onProviderChanged)
- self.providerCheckButton.clicked.connect(
- self.onCheckButtonClicked)
-
- layout = QtGui.QGridLayout()
- layout.addWidget(validationMsg, 0, 2)
- layout.addWidget(providerNameLabel, 1, 1)
- layout.addWidget(providerNameEdit, 1, 2)
- layout.addWidget(providerCheckButton, 1, 3)
-
- # add certinfo group
- # XXX not shown now. should move to validation box.
- #layout.addWidget(certinfoGroup, 4, 1, 4, 2)
- #self.certinfoGroup = certinfoGroup
- #self.certinfoGroup.hide()
-
- # add validation frame
- self.setupValidationFrame()
- layout.addWidget(self.valFrame, 4, 2, 4, 2)
- self.valFrame.hide()
-
- self.setLayout(layout)
-
- # certinfo
-
- def setupCertInfoGroup(self): # pragma: no cover
- # XXX not used now.
- certinfoGroup = QtGui.QGroupBox(
- self.tr("Certificate validation"))
- certinfoLayout = QtGui.QVBoxLayout()
- certinfoLayout.addWidget(self.certInfo)
- certinfoLayout.addWidget(self.certWarning)
- certinfoLayout.addWidget(self.trustProviderCertCheckBox)
- certinfoGroup.setLayout(certinfoLayout)
- self.certinfoGroup = self.certinfoGroup
-
- # progress frame
-
- def setupValidationFrame(self):
- qframe = QtGui.QFrame
- valFrame = qframe()
- valFrame.setFrameStyle(qframe.NoFrame)
- valframeLayout = QtGui.QVBoxLayout()
- zeros = (0, 0, 0, 0)
- valframeLayout.setContentsMargins(*zeros)
-
- valframeLayout.addWidget(self.stepsTableWidget)
- valFrame.setLayout(valframeLayout)
- self.valFrame = valFrame
-
- @QtCore.pyqtSlot()
- def onDisableCheckButton(self):
- #print 'CHECK BUTTON DISABLED!!!'
- self.providerCheckButton.setDisabled(True)
-
- @QtCore.pyqtSlot()
- def launch_checks(self):
- self.do_checks()
-
- def onCheckButtonClicked(self):
- QtCore.QMetaObject.invokeMethod(
- self, "onDisableCheckButton")
-
- QtCore.QMetaObject.invokeMethod(
- self, "showStepsFrame")
-
- delay(self, "launch_checks")
-
- def _do_checks(self):
- """
- generator that yields actual checks
- that are executed in a separate thread
- """
-
- wizard = self.wizard()
- full_domain = self.providerNameEdit.text()
-
- # we check if we have a port in the domain string.
- domain, port = get_https_domain_and_port(full_domain)
- _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain)
-
- netchecker = wizard.netchecker()
- providercertchecker = wizard.providercertchecker()
- eipconfigchecker = wizard.eipconfigchecker(domain=_domain)
-
- yield(("head_sentinel", 0), lambda: None)
-
- ########################
- # 1) try name resolution
- ########################
-
- def namecheck():
- """
- in which we check if
- we are able to name resolve
- this domain
- """
- try:
- #import ipdb;ipdb.set_trace()
- netchecker.check_name_resolution(
- domain)
-
- except baseexceptions.LeapException as exc:
- logger.error(exc.message)
- return self.fail(exc.usermessage)
-
- except Exception as exc:
- return self.fail(exc.message)
-
- else:
- return True
-
- logger.debug('checking name resolution')
- yield((self.tr("Checking if it is a valid provider"), 20), namecheck)
-
- #########################
- # 2) try https connection
- #########################
-
- def httpscheck():
- """
- in which we check
- if the provider
- is offering service over
- https
- """
- try:
- providercertchecker.is_https_working(
- "https://%s" % _domain,
- verify=True)
-
- except eipexceptions.HttpsBadCertError as exc:
- logger.debug('exception')
- return self.fail(exc.usermessage)
- # XXX skipping for now...
- ##############################################
- # We had this validation logic
- # in the provider selection page before
- ##############################################
- #if self.trustProviderCertCheckBox.isChecked():
- #pass
- #else:
- #fingerprint = certs.get_cert_fingerprint(
- #domain=domain, sep=" ")
-
- # it's ok if we've trusted this fgprt before
- #trustedcrts = wizard.trusted_certs
- #if trustedcrts and \
- # fingerprint.replace(' ', '') in trustedcrts:
- #pass
- #else:
- # let your user face panick :P
- #self.add_cert_info(fingerprint)
- #self.did_cert_check = True
- #self.completeChanged.emit()
- #return False
-
- except baseexceptions.LeapException as exc:
- return self.fail(exc.usermessage)
-
- except Exception as exc:
- return self.fail(exc.message)
-
- else:
- return True
-
- logger.debug('checking https connection')
- yield((self.tr("Checking for a secure connection"), 40), httpscheck)
-
- ##################################
- # 3) try download provider info...
- ##################################
-
- def fetchinfo():
- try:
- # XXX we already set _domain in the initialization
- # so it should not be needed here.
- eipconfigchecker.fetch_definition(domain=_domain)
- wizard.set_providerconfig(
- eipconfigchecker.defaultprovider.config)
- except requests.exceptions.SSLError:
- return self.fail(self.tr(
- "Could not get info from provider."))
- except requests.exceptions.ConnectionError:
- return self.fail(self.tr(
- "Could not download provider info "
- "(refused conn.)."))
-
- except Exception as exc:
- return self.fail(
- self.tr(exc.message))
- else:
- return True
-
- yield((self.tr("Getting info from the provider"), 80), fetchinfo)
-
- # done!
-
- self.done = True
- yield(("end_sentinel", 100), lambda: None)
-
- def on_checks_validation_ready(self):
- """
- called after _do_checks has finished.
- """
- self.domain_checked = True
- self.completeChanged.emit()
- # let's set focus...
- if self.is_done():
- self.wizard().clean_validation_error(self.current_page)
- nextbutton = self.wizard().button(QtGui.QWizard.NextButton)
- nextbutton.setFocus()
- else:
- self.providerNameEdit.setFocus()
-
- # cert trust verification
- # (disabled for now)
-
- def is_insecure_cert_trusted(self):
- return self.trustProviderCertCheckBox.isChecked()
-
- def onTrustCheckChanged(self, state): # pragma: no cover XXX
- checked = False
- if state == 2:
- checked = True
-
- if checked:
- self.reset_validation_status()
- else:
- self.set_validation_status(self.bad_cert_status)
-
- # trigger signal to redraw next button
- self.completeChanged.emit()
-
- def add_cert_info(self, certinfo): # pragma: no cover XXX
- self.certWarning.setText(
- self.tr("Do you want to <b>trust this provider certificate?</b>"))
- # XXX Check if this needs to abstracted to remove certinfo
- self.certInfo.setText(
- self.tr('SHA-256 fingerprint: <i>%s</i><br>' % certinfo))
- self.certInfo.setWordWrap(True)
- self.certinfoGroup.show()
-
- def onProviderChanged(self, text):
- self.done = False
- provider = self.providerNameEdit.text()
- if provider:
- self.providerCheckButton.setDisabled(False)
- else:
- self.providerCheckButton.setDisabled(True)
- self.completeChanged.emit()
-
- def reset_validation_status(self):
- """
- empty the validation msg
- and clean the inline validation widget.
- """
- self.validationMsg.setText('')
- self.steps.removeAllSteps()
- self.clearTable()
- self.domain_checked = False
-
- # pagewizard methods
-
- def isComplete(self):
- provider = self.providerNameEdit.text()
-
- if not self.is_done():
- return False
-
- if not provider:
- return False
- else:
- if self.is_insecure_cert_trusted():
- return True
- if not self.did_cert_check:
- if self.is_done():
- # XXX sure?
- return True
- return False
-
- def populateErrors(self):
- # XXX could move this to ValidationMixin
- # with some defaults for the validating fields
- # (now it only allows one field, manually specified)
-
- #logger.debug('getting errors')
- errors = self.wizard().get_validation_error(
- self.current_page)
- if errors:
- bad_str = getattr(self, 'bad_string', None)
- cur_str = self.providerNameEdit.text()
- showerr = self.validationMsg.setText
- markred = lambda: self.providerNameEdit.setStyleSheet(
- styles.ErrorLineEdit)
- umarkrd = lambda: self.providerNameEdit.setStyleSheet(
- styles.RegularLineEdit)
- if bad_str is None:
- # first time we fall here.
- # save the current bad_string value
- self.bad_string = cur_str
- showerr(errors)
- markred()
- else:
- # not the first time
- # XXX hey, this is getting convoluted.
- # roll out this.
- # but be careful about all the possibilities
- # with going back and forth once you
- # enter a domain.
- if cur_str == bad_str:
- showerr(errors)
- markred()
- else:
- if not getattr(self, 'domain_checked', None):
- showerr('')
- umarkrd()
- else:
- self.bad_string = cur_str
- showerr(errors)
-
- def cleanup_errormsg(self):
- """
- we reset bad_string to None
- should be called before leaving the page
- """
- self.bad_string = None
- self.domain_checked = False
-
- def paintEvent(self, event):
- """
- we hook our populate errors
- on paintEvent because we need it to catch
- when user enters the page coming from next,
- and initializePage does not cover that case.
- Maybe there's a better event to hook upon.
- """
- super(SelectProviderPage, self).paintEvent(event)
- self.populateErrors()
-
- def initializePage(self):
- self.validationMsg.setText('')
- if hasattr(self, 'certinfoGroup'):
- # XXX remove ?
- self.certinfoGroup.hide()
- self.done = False
- self.providerCheckButton.setDisabled(True)
- self.valFrame.hide()
- self.steps.removeAllSteps()
- self.clearTable()
-
- def validatePage(self):
- # some cleanup before we leave the page
- self.cleanup_errormsg()
-
- # go
- return True
-
- def nextId(self):
- wizard = self.wizard()
- if not wizard:
- return
- return wizard.get_page_index('providerinfo')
diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py
deleted file mode 100644
index 47060f6e..00000000
--- a/src/leap/gui/firstrun/providersetup.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""
-Provider Setup Validation Page,
-used if First Run Wizard
-"""
-import logging
-
-import requests
-
-from PyQt4 import QtGui
-
-from leap.base import exceptions as baseexceptions
-from leap.gui.progress import ValidationPage
-
-from leap.gui.constants import APP_LOGO
-
-logger = logging.getLogger(__name__)
-
-
-class ProviderSetupValidationPage(ValidationPage):
- def __init__(self, parent=None):
- super(ProviderSetupValidationPage, self).__init__(parent)
- self.current_page = "providersetupvalidation"
-
- # XXX needed anymore?
- #is_signup = self.field("is_signup")
- #self.is_signup = is_signup
-
- self.setTitle(self.tr("Provider setup"))
- self.setSubTitle(
- self.tr("Gathering configuration options for this provider"))
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- def _do_checks(self):
- """
- generator that yields actual checks
- that are executed in a separate thread
- """
-
- full_domain = self.field('provider_domain')
- wizard = self.wizard()
- pconfig = wizard.providerconfig
-
- #pCertChecker = wizard.providercertchecker
- #certchecker = pCertChecker(domain=full_domain)
- pCertChecker = wizard.providercertchecker(
- domain=full_domain)
-
- yield(("head_sentinel", 0), lambda: None)
-
- ########################
- # 1) fetch ca cert
- ########################
-
- def fetchcacert():
- if pconfig:
- ca_cert_uri = pconfig.get('ca_cert_uri').geturl()
- else:
- ca_cert_uri = None
-
- # XXX check scheme == "https"
- # XXX passing verify == False because
- # we have trusted right before.
- # We should check it's the same domain!!!
- # (Check with the trusted fingerprints dict
- # or something smart)
- try:
- pCertChecker.download_ca_cert(
- uri=ca_cert_uri,
- verify=False)
-
- except baseexceptions.LeapException as exc:
- logger.error(exc.message)
- # XXX this should be _ method
- return self.fail(self.tr(exc.usermessage))
-
- except Exception as exc:
- return self.fail(exc.message)
-
- else:
- return True
-
- yield((self.tr('Fetching CA certificate'), 30),
- fetchcacert)
-
- #########################
- # 2) check CA fingerprint
- #########################
-
- def checkcafingerprint():
- # XXX get the real thing!!!
- pass
- #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None)
-
- # XXX get fingerprint dict (types)
- #sha256_fpr = ca_cert_fingerprint.split('=')[1]
-
- #validate_fpr = pCertChecker.check_ca_cert_fingerprint(
- #fingerprint=sha256_fpr)
- #if not validate_fpr:
- # XXX update validationMsg
- # should catch exception
- #return False
-
- yield((self.tr("Checking CA fingerprint"), 60),
- checkcafingerprint)
-
- #########################
- # 2) check CA fingerprint
- #########################
-
- def validatecacert():
- api_uri = pconfig.get('api_uri', None)
- try:
- pCertChecker.verify_api_https(api_uri)
- except requests.exceptions.SSLError as exc:
- return self.fail("Validation Error")
- except Exception as exc:
- return self.fail(exc.msg)
- else:
- return True
-
- yield((self.tr('Validating api certificate'), 90), validatecacert)
-
- self.set_done()
- yield(('end_sentinel', 100), lambda: None)
-
- def on_checks_validation_ready(self):
- """
- called after _do_checks has finished
- (connected to checker thread finished signal)
- """
- wizard = self.wizard()
- prevpage = "login" if wizard.from_login else "providerselection"
-
- if self.errors:
- logger.debug('going back with errors')
- name, first_error = self.pop_first_error()
- wizard.set_validation_error(
- prevpage,
- first_error)
-
- def nextId(self):
- wizard = self.wizard()
- from_login = wizard.from_login
- if from_login:
- next_ = 'connect'
- else:
- next_ = 'signup'
- return wizard.get_page_index(next_)
-
- def initializePage(self):
- super(ProviderSetupValidationPage, self).initializePage()
- self.set_undone()
- self.completeChanged.emit()
diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py
deleted file mode 100644
index 15278330..00000000
--- a/src/leap/gui/firstrun/register.py
+++ /dev/null
@@ -1,387 +0,0 @@
-"""
-Register User Page, used in First Run Wizard
-"""
-import json
-import logging
-import socket
-
-import requests
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap.gui.firstrun.mixins import UserFormMixIn
-
-logger = logging.getLogger(__name__)
-
-from leap.base import auth
-from leap.gui import styles
-from leap.gui.constants import APP_LOGO, BARE_USERNAME_REGEX
-from leap.gui.progress import InlineValidationPage
-from leap.gui.styles import ErrorLabelStyleSheet
-
-
-class RegisterUserPage(InlineValidationPage, UserFormMixIn):
-
- def __init__(self, parent=None):
-
- super(RegisterUserPage, self).__init__(parent)
- self.current_page = "signup"
-
- self.setTitle(self.tr("Sign Up"))
- # subtitle is set in the initializePage
-
- self.setPixmap(
- QtGui.QWizard.LogoPixmap,
- QtGui.QPixmap(APP_LOGO))
-
- # commit page means there's no way back after this...
- # XXX should change the text on the "commit" button...
- self.setCommitPage(True)
-
- self.setupSteps()
- self.setupUI()
- self.do_confirm_next = False
- self.focused_field = False
-
- def setupUI(self):
- userNameLabel = QtGui.QLabel(self.tr("User &name:"))
- userNameLineEdit = QtGui.QLineEdit()
- userNameLineEdit.cursorPositionChanged.connect(
- self.reset_validation_status)
- userNameLabel.setBuddy(userNameLineEdit)
-
- # let's add regex validator
- usernameRe = QtCore.QRegExp(BARE_USERNAME_REGEX)
- userNameLineEdit.setValidator(
- QtGui.QRegExpValidator(usernameRe, self))
- self.userNameLineEdit = userNameLineEdit
-
- userPasswordLabel = QtGui.QLabel(self.tr("&Password:"))
- self.userPasswordLineEdit = QtGui.QLineEdit()
- self.userPasswordLineEdit.setEchoMode(
- QtGui.QLineEdit.Password)
- userPasswordLabel.setBuddy(self.userPasswordLineEdit)
-
- userPassword2Label = QtGui.QLabel(self.tr("Password (again):"))
- self.userPassword2LineEdit = QtGui.QLineEdit()
- self.userPassword2LineEdit.setEchoMode(
- QtGui.QLineEdit.Password)
- userPassword2Label.setBuddy(self.userPassword2LineEdit)
-
- rememberPasswordCheckBox = QtGui.QCheckBox(
- self.tr("&Remember username and password."))
- rememberPasswordCheckBox.setChecked(True)
-
- self.registerField('userName*', self.userNameLineEdit)
- self.registerField('userPassword*', self.userPasswordLineEdit)
- self.registerField('userPassword2*', self.userPassword2LineEdit)
-
- # XXX missing password confirmation
- # XXX validator!
-
- self.registerField('rememberPassword', rememberPasswordCheckBox)
-
- layout = QtGui.QGridLayout()
- layout.setColumnMinimumWidth(0, 20)
-
- validationMsg = QtGui.QLabel("")
- validationMsg.setStyleSheet(ErrorLabelStyleSheet)
-
- self.validationMsg = validationMsg
-
- layout.addWidget(validationMsg, 0, 3)
- layout.addWidget(userNameLabel, 1, 0)
- layout.addWidget(self.userNameLineEdit, 1, 3)
- layout.addWidget(userPasswordLabel, 2, 0)
- layout.addWidget(userPassword2Label, 3, 0)
- layout.addWidget(self.userPasswordLineEdit, 2, 3)
- layout.addWidget(self.userPassword2LineEdit, 3, 3)
- layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4)
-
- # add validation frame
- self.setupValidationFrame()
- layout.addWidget(self.valFrame, 5, 2, 5, 2)
- self.valFrame.hide()
-
- self.setLayout(layout)
- self.commitText("Sign up!")
-
- # commit button
-
- def commitText(self, text):
- # change "commit" button text
- self.setButtonText(
- QtGui.QWizard.CommitButton, text)
-
- @property
- def commitButton(self):
- return self.wizard().button(QtGui.QWizard.CommitButton)
-
- def commitFocus(self):
- self.commitButton.setFocus()
-
- def disableCommitButton(self):
- self.commitButton.setDisabled(True)
-
- def disableFields(self):
- for field in (self.userNameLineEdit,
- self.userPasswordLineEdit,
- self.userPassword2LineEdit):
- field.setDisabled(True)
-
- # error painting
- def paintEvent(self, event):
- """
- we hook our populate errors
- on paintEvent because we need it to catch
- when user enters the page coming from next,
- and initializePage does not cover that case.
- Maybe there's a better event to hook upon.
- """
- super(RegisterUserPage, self).paintEvent(event)
- self.populateErrors()
-
- def markRedAndGetFocus(self, field):
- field.setStyleSheet(styles.ErrorLineEdit)
- if not self.focused_field:
- self.focused_field = True
- field.setFocus(QtCore.Qt.OtherFocusReason)
-
- def markRegular(self, field):
- field.setStyleSheet(styles.RegularLineEdit)
-
- def populateErrors(self):
- def showerr(text):
- self.validationMsg.setText(text)
- err_lower = text.lower()
- if "username" in err_lower:
- self.markRedAndGetFocus(
- self.userNameLineEdit)
- if "password" in err_lower:
- self.markRedAndGetFocus(
- self.userPasswordLineEdit)
-
- def unmarkred():
- for field in (self.userNameLineEdit,
- self.userPasswordLineEdit,
- self.userPassword2LineEdit):
- self.markRegular(field)
-
- errors = self.wizard().get_validation_error(
- self.current_page)
- if errors:
- bad_str = getattr(self, 'bad_string', None)
- cur_str = self.userNameLineEdit.text()
- #prev_er = getattr(self, 'prevalidation_error', None)
-
- if bad_str is None:
- # first time we fall here.
- # save the current bad_string value
- self.bad_string = cur_str
- showerr(errors)
- else:
- #if prev_er:
- #showerr(prev_er)
- #return
- # not the first time
- if cur_str == bad_str:
- showerr(errors)
- else:
- self.focused_field = False
- showerr('')
- unmarkred()
- else:
- # no errors
- self.focused_field = False
- unmarkred()
-
- def cleanup_errormsg(self):
- """
- we reset bad_string to None
- should be called before leaving the page
- """
- self.bad_string = None
-
- def green_validation_status(self):
- val = self.validationMsg
- val.setText(self.tr('Registration succeeded!'))
- val.setStyleSheet(styles.GreenLineEdit)
-
- def reset_validation_status(self):
- """
- empty the validation msg
- and clean the inline validation widget.
- """
- self.validationMsg.setText('')
- self.steps.removeAllSteps()
- self.clearTable()
-
- # actual checks
-
- def _do_checks(self):
- """
- generator that yields actual checks
- that are executed in a separate thread
- """
- wizard = self.wizard()
-
- provider = self.field('provider_domain')
- username = self.userNameLineEdit.text()
- password = self.userPasswordLineEdit.text()
- password2 = self.userPassword2LineEdit.text()
-
- pconfig = wizard.eipconfigchecker(domain=provider)
- pconfig.defaultprovider.load()
- pconfig.set_api_domain()
-
- def checkpass():
- # we better have here
- # some call to a password checker...
- # to assess strenght and avoid silly stuff.
-
- if password != password2:
- return self.fail(self.tr('Password does not match..'))
-
- if len(password) < 6:
- #self.set_prevalidation_error('Password too short.')
- return self.fail(self.tr('Password too short.'))
-
- if password == "123456":
- # joking, but not too much.
- #self.set_prevalidation_error('Password too obvious.')
- return self.fail(self.tr('Password too obvious.'))
-
- # go
- return True
-
- yield(("head_sentinel", 0), checkpass)
-
- # XXX should emit signal for .show the frame!
- # XXX HERE!
-
- ##################################################
- # 1) register user
- ##################################################
-
- # show the frame before going on...
- QtCore.QMetaObject.invokeMethod(
- self, "showStepsFrame")
-
- def register():
-
- signup = auth.LeapSRPRegister(
- schema="https",
- provider=pconfig.apidomain,
- verify=pconfig.cacert)
- try:
- ok, req = signup.register_user(
- username, password)
-
- except socket.timeout:
- return self.fail(
- self.tr("Error connecting to provider (timeout)"))
-
- except requests.exceptions.ConnectionError as exc:
- logger.error(exc.message)
- return self.fail(
- self.tr('Error Connecting to provider (connerr).'))
- except Exception as exc:
- return self.fail(exc.message)
-
- # XXX check for != OK instead???
-
- if req.status_code in (404, 500):
- return self.fail(
- self.tr(
- "Error during registration (%s)") % req.status_code)
-
- try:
- validation_msgs = json.loads(req.content)
- errors = validation_msgs.get('errors', None)
- logger.debug('validation errors: %s' % validation_msgs)
- except ValueError:
- # probably bad json returned
- return self.fail(
- self.tr(
- "Could not register (bad response)"))
-
- if errors and errors.get('login', None):
- # XXX this sometimes catch the blank username
- # but we're not allowing that (soon)
- return self.fail(
- self.tr('Username not available.'))
-
- return True
-
- logger.debug('registering user')
- yield(("Registering username", 40), register)
-
- self.set_done()
- yield(("end_sentinel", 100), lambda: None)
-
- def on_checks_validation_ready(self):
- """
- after checks
- """
- if self.is_done():
- self.disableFields()
- self.cleanup_errormsg()
- self.clean_wizard_errors(self.current_page)
- # make the user confirm the transition
- # to next page.
- self.commitText('Connect!')
- self.commitFocus()
- self.green_validation_status()
- self.do_confirm_next = True
-
- # pagewizard methods
-
- def validatePage(self):
- """
- if not register done, do checks.
- if done, wait for click.
- """
- self.disableCommitButton()
- self.cleanup_errormsg()
- self.clean_wizard_errors(self.current_page)
-
- # After a successful validation
- # (ie, success register with server)
- # we change the commit button text
- # and set this flag to True.
- if self.do_confirm_next:
- return True
-
- if not self.is_done():
- # calls checks, which after successful
- # execution will call on_checks_validation_ready
- self.reset_validation_status()
- self.do_checks()
-
- return self.is_done()
-
- def initializePage(self):
- """
- inits wizard page
- """
- provider = unicode(self.field('provider_domain'))
- if provider:
- # here we should have provider
- # but in tests we might not.
-
- # XXX this error causes a segfault on free()
- # that we might want to get fixed ...
- #self.setSubTitle(
- #self.tr("Register a new user with provider %s.") %
- #provider)
- self.setSubTitle(
- self.tr("Register a new user with provider <em>%s</em>" %
- provider))
- self.validationMsg.setText('')
- self.userPassword2LineEdit.setText('')
- self.valFrame.hide()
-
- def nextId(self):
- wizard = self.wizard()
- return wizard.get_page_index('connect')
diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py
deleted file mode 100755
index 668db5d1..00000000
--- a/src/leap/gui/firstrun/tests/integration/fake_provider.py
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/usr/bin/env python
-"""A server faking some of the provider resources and apis,
-used for testing Leap Client requests
-
-It needs that you create a subfolder named 'certs',
-and that you place the following files:
-
-[ ] certs/leaptestscert.pem
-[ ] certs/leaptestskey.pem
-[ ] certs/cacert.pem
-[ ] certs/openvpn.pem
-
-[ ] provider.json
-[ ] eip-service.json
-"""
-# XXX NOTE: intended for manual debug.
-# I intend to include this as a regular test after 0.2.0 release
-# (so we can add twisted as a dep there)
-import binascii
-import json
-import os
-import sys
-
-# python SRP LIB (! important MUST be >=1.0.1 !)
-import srp
-
-# GnuTLS Example -- is not working as expected
-#from gnutls import crypto
-#from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL
-#from gnutls.interfaces.twisted import X509Credentials
-
-# Going with OpenSSL as a workaround instead
-# But we DO NOT want to introduce this dependency.
-from OpenSSL import SSL
-
-from zope.interface import Interface, Attribute, implements
-
-from twisted.web.server import Site
-from twisted.web.static import File
-from twisted.web.resource import Resource
-from twisted.internet import reactor
-
-from leap.testing.https_server import where
-
-# See
-# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln
-# for more examples
-
-"""
-Testing the FAKE_API:
-#####################
-
- 1) register an user
- >> curl -d "user[login]=me" -d "user[password_salt]=foo" \
- -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
- << {"errors": null}
-
- 2) check that if you try to register again, it will fail:
- >> curl -d "user[login]=me" -d "user[password_salt]=foo" \
- -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
- << {"errors": {"login": "already taken!"}}
-
-"""
-
-# Globals to mock user/sessiondb
-
-USERDB = {}
-SESSIONDB = {}
-
-
-safe_unhexlify = lambda x: binascii.unhexlify(x) \
- if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
-
-
-class IUser(Interface):
- login = Attribute("User login.")
- salt = Attribute("Password salt.")
- verifier = Attribute("Password verifier.")
- session = Attribute("Session.")
- svr = Attribute("Server verifier.")
-
-
-class User(object):
- implements(IUser)
-
- def __init__(self, login, salt, verifier):
- self.login = login
- self.salt = salt
- self.verifier = verifier
- self.session = None
-
- def set_server_verifier(self, svr):
- self.svr = svr
-
- def set_session(self, session):
- SESSIONDB[session] = self
- self.session = session
-
-
-class FakeUsers(Resource):
- def __init__(self, name):
- self.name = name
-
- def render_POST(self, request):
- args = request.args
-
- login = args['user[login]'][0]
- salt = args['user[password_salt]'][0]
- verifier = args['user[password_verifier]'][0]
-
- if login in USERDB:
- return "%s\n" % json.dumps(
- {'errors': {'login': 'already taken!'}})
-
- print login, verifier, salt
- user = User(login, salt, verifier)
- USERDB[login] = user
- return json.dumps({'errors': None})
-
-
-def get_user(request):
- login = request.args.get('login')
- if login:
- user = USERDB.get(login[0], None)
- if user:
- return user
-
- session = request.getSession()
- user = SESSIONDB.get(session, None)
- return user
-
-
-class FakeSession(Resource):
- def __init__(self, name):
- self.name = name
-
- def render_GET(self, request):
- return "%s\n" % json.dumps({'errors': None})
-
- def render_POST(self, request):
-
- user = get_user(request)
-
- if not user:
- # XXX get real error from demo provider
- return json.dumps({'errors': 'no such user'})
-
- A = request.args['A'][0]
-
- _A = safe_unhexlify(A)
- _salt = safe_unhexlify(user.salt)
- _verifier = safe_unhexlify(user.verifier)
-
- svr = srp.Verifier(
- user.login,
- _salt,
- _verifier,
- _A,
- hash_alg=srp.SHA256,
- ng_type=srp.NG_1024)
-
- s, B = svr.get_challenge()
-
- _B = binascii.hexlify(B)
-
- print 'login = %s' % user.login
- print 'salt = %s' % user.salt
- print 'len(_salt) = %s' % len(_salt)
- print 'vkey = %s' % user.verifier
- print 'len(vkey) = %s' % len(_verifier)
- print 's = %s' % binascii.hexlify(s)
- print 'B = %s' % _B
- print 'len(B) = %s' % len(_B)
-
- session = request.getSession()
- user.set_session(session)
- user.set_server_verifier(svr)
-
- # yep, this is tricky.
- # some things are *already* unhexlified.
- data = {
- 'salt': user.salt,
- 'B': _B,
- 'errors': None}
-
- return json.dumps(data)
-
- def render_PUT(self, request):
-
- # XXX check session???
- user = get_user(request)
-
- if not user:
- print 'NO USER'
- return json.dumps({'errors': 'no such user'})
-
- data = request.content.read()
- auth = data.split("client_auth=")
- M = auth[1] if len(auth) > 1 else None
- # if not H, return
- if not M:
- return json.dumps({'errors': 'no M proof passed by client'})
-
- svr = user.svr
- HAMK = svr.verify_session(binascii.unhexlify(M))
- if HAMK is None:
- print 'verification failed!!!'
- raise Exception("Authentication failed!")
- #import ipdb;ipdb.set_trace()
-
- assert svr.authenticated()
- print "***"
- print 'server authenticated user SRP!'
- print "***"
-
- return json.dumps(
- {'M2': binascii.hexlify(HAMK), 'errors': None})
-
-
-class API_Sessions(Resource):
- def getChild(self, name, request):
- return FakeSession(name)
-
-
-def get_certs_path():
- script_path = os.path.realpath(os.path.dirname(sys.argv[0]))
- certs_path = os.path.join(script_path, 'certs')
- return certs_path
-
-
-def get_TLS_credentials():
- # XXX this is giving errors
- # XXX REview! We want to use gnutls!
-
- cert = crypto.X509Certificate(
- open(where('leaptestscert.pem')).read())
- key = crypto.X509PrivateKey(
- open(where('leaptestskey.pem')).read())
- ca = crypto.X509Certificate(
- open(where('cacert.pem')).read())
- #crl = crypto.X509CRL(open(certs_path + '/crl.pem').read())
- #cred = crypto.X509Credentials(cert, key, [ca], [crl])
- cred = X509Credentials(cert, key, [ca])
- cred.verify_peer = True
- cred.session_params.compressions = (COMP_LZO, COMP_DEFLATE, COMP_NULL)
- return cred
-
-
-class OpenSSLServerContextFactory:
- # XXX workaround for broken TLS interface
- # from gnuTLS.
-
- def getContext(self):
- """Create an SSL context.
- This is a sample implementation that loads a certificate from a file
- called 'server.pem'."""
-
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- #certs_path = get_certs_path()
- #ctx.use_certificate_file(certs_path + '/leaptestscert.pem')
- #ctx.use_privatekey_file(certs_path + '/leaptestskey.pem')
- ctx.use_certificate_file(where('leaptestscert.pem'))
- ctx.use_privatekey_file(where('leaptestskey.pem'))
- return ctx
-
-
-def serve_fake_provider():
- root = Resource()
- root.putChild("provider.json", File("./provider.json"))
- config = Resource()
- config.putChild(
- "eip-service.json",
- File("./eip-service.json"))
- apiv1 = Resource()
- apiv1.putChild("config", config)
- apiv1.putChild("sessions.json", API_Sessions())
- apiv1.putChild("users.json", FakeUsers(None))
- apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem'))
- root.putChild("1", apiv1)
-
- cred = get_TLS_credentials()
-
- factory = Site(root)
-
- # regular http (for debugging with curl)
- reactor.listenTCP(8000, factory)
-
- # TLS with gnutls --- seems broken :(
- #reactor.listenTLS(8003, factory, cred)
-
- # OpenSSL
- reactor.listenSSL(8443, factory, OpenSSLServerContextFactory())
-
- reactor.run()
-
-
-if __name__ == "__main__":
-
- from twisted.python import log
- log.startLogging(sys.stdout)
-
- serve_fake_provider()
diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py
deleted file mode 100755
index f198dca0..00000000
--- a/src/leap/gui/firstrun/wizard.py
+++ /dev/null
@@ -1,309 +0,0 @@
-#!/usr/bin/env python
-import logging
-
-import sip
-try:
- sip.setapi('QString', 2)
- sip.setapi('QVariant', 2)
-except ValueError:
- pass
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap.base import checks as basechecks
-from leap.crypto import leapkeyring
-from leap.eip import checks as eipchecks
-
-from leap.gui import firstrun
-
-from leap.gui import mainwindow_rc
-
-try:
- from collections import OrderedDict
-except ImportError:
- # We must be in 2.6
- from leap.util.dicts import OrderedDict
-
-logger = logging.getLogger(__name__)
-
-"""
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-Work in progress!
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-This wizard still needs to be refactored out.
-
-TODO-ish:
-
-[X] Break file in wizard / pages files (and its own folder).
-[ ] Separate presentation from logic.
-[ ] Have a "manager" class for connections, that can be
- dep-injected for testing.
-[ ] Document signals used / expected.
-[ ] Separate style from widgets.
-[ ] Fix TOFU Widget for provider cert.
-[X] Refactor widgets out.
-[ ] Follow more MVC style.
-[ ] Maybe separate "first run wizard" into different wizards
- that share some of the pages?
-"""
-
-
-def get_pages_dict():
- return OrderedDict((
- ('intro', firstrun.intro.IntroPage),
- ('providerselection',
- firstrun.providerselect.SelectProviderPage),
- ('login', firstrun.login.LogInPage),
- ('providerinfo', firstrun.providerinfo.ProviderInfoPage),
- ('providersetupvalidation',
- firstrun.providersetup.ProviderSetupValidationPage),
- ('signup', firstrun.register.RegisterUserPage),
- ('connect',
- firstrun.connect.ConnectionPage),
- ('lastpage', firstrun.last.LastPage)
- ))
-
-
-class FirstRunWizard(QtGui.QWizard):
-
- def __init__(
- self,
- conductor_instance,
- parent=None,
- pages_dict=None,
- username=None,
- providers=None,
- success_cb=None, is_provider_setup=False,
- trusted_certs=None,
- netchecker=basechecks.LeapNetworkChecker,
- providercertchecker=eipchecks.ProviderCertChecker,
- eipconfigchecker=eipchecks.EIPConfigChecker,
- start_eipconnection_signal=None,
- eip_statuschange_signal=None,
- debug_server=None,
- quitcallback=None):
- super(FirstRunWizard, self).__init__(
- parent,
- QtCore.Qt.WindowStaysOnTopHint)
-
- # we keep a reference to the conductor
- # to be able to launch eip checks and connection
- # in the connection page, before the wizard has ended.
- self.conductor = conductor_instance
-
- self.username = username
- self.providers = providers
-
- # success callback
- self.success_cb = success_cb
-
- # is provider setup?
- self.is_provider_setup = is_provider_setup
-
- # a dict with trusted fingerprints
- # in the form {'nospacesfingerprint': ['host1', 'host2']}
- self.trusted_certs = trusted_certs
-
- # Checkers
- self.netchecker = netchecker
- self.providercertchecker = providercertchecker
- self.eipconfigchecker = eipconfigchecker
-
- # debug server
- self.debug_server = debug_server
-
- # Signals
- # will be emitted in connecting page
- self.start_eipconnection_signal = start_eipconnection_signal
- self.eip_statuschange_signal = eip_statuschange_signal
-
- if quitcallback is not None:
- self.button(
- QtGui.QWizard.CancelButton).clicked.connect(
- quitcallback)
-
- self.providerconfig = None
- # previously registered
- # if True, jumps to LogIn page.
- # by setting 1st page??
- #self.is_previously_registered = is_previously_registered
- # XXX ??? ^v
- self.is_previously_registered = bool(self.username)
- self.from_login = False
-
- pages_dict = pages_dict or get_pages_dict()
- self.add_pages_from_dict(pages_dict)
-
- self.validation_errors = {}
- self.openvpn_status = []
-
- self.setPixmap(
- QtGui.QWizard.BannerPixmap,
- QtGui.QPixmap(':/images/banner.png'))
- self.setPixmap(
- QtGui.QWizard.BackgroundPixmap,
- QtGui.QPixmap(':/images/background.png'))
-
- # set options
- self.setOption(QtGui.QWizard.IndependentPages, on=False)
- self.setOption(QtGui.QWizard.NoBackButtonOnStartPage, on=True)
-
- self.setWindowTitle("First Run Wizard")
-
- # TODO: set style for MAC / windows ...
- #self.setWizardStyle()
-
- #
- # setup pages in wizard
- #
-
- def add_pages_from_dict(self, pages_dict):
- """
- @param pages_dict: the dictionary with pages, where
- values are a tuple of InstanceofWizardPage, kwargs.
- @type pages_dict: dict
- """
- for name, page in pages_dict.items():
- # XXX check for is_previously registered
- # and skip adding the signup branch if so
- self.addPage(page())
- self.pages_dict = pages_dict
-
- def get_page_index(self, page_name):
- """
- returns the index of the given page
- @param page_name: the name of the desired page
- @type page_name: str
- @rparam: index of page in wizard
- @rtype: int
- """
- return self.pages_dict.keys().index(page_name)
-
- #
- # validation errors
- #
-
- def set_validation_error(self, pagename, error):
- self.validation_errors[pagename] = error
-
- def clean_validation_error(self, pagename):
- vald = self.validation_errors
- if pagename in vald:
- del vald[pagename]
-
- def get_validation_error(self, pagename):
- return self.validation_errors.get(pagename, None)
-
- def accept(self):
- """
- final step in the wizard.
- gather the info, update settings
- and call the success callback if any has been passed.
- """
- super(FirstRunWizard, self).accept()
-
- # username and password are in different fields
- # if they were stored in log_in or sign_up pages.
- from_login = self.from_login
- unamek_base = 'userName'
- passwk_base = 'userPassword'
- unamek = 'login_%s' % unamek_base if from_login else unamek_base
- passwk = 'login_%s' % passwk_base if from_login else passwk_base
-
- username = self.field(unamek)
- password = self.field(passwk)
- provider = self.field('provider_domain')
- remember_pass = self.field('rememberPassword')
-
- logger.debug('chosen provider: %s', provider)
- logger.debug('username: %s', username)
- logger.debug('remember password: %s', remember_pass)
-
- # we are assuming here that we only remember one username
- # in the form username@provider.domain
- # We probably could extend this to support some form of
- # profiles.
-
- settings = QtCore.QSettings()
-
- settings.setValue("FirstRunWizardDone", True)
- settings.setValue("provider_domain", provider)
- full_username = "%s@%s" % (username, provider)
-
- settings.setValue("remember_user_and_pass", remember_pass)
-
- if remember_pass:
- settings.setValue("username", full_username)
- seed = self.get_random_str(10)
- settings.setValue("%s_seed" % provider, seed)
-
- # XXX #744: comment out for 0.2.0 release
- # if we need to have a version of python-keyring < 0.9
- leapkeyring.leap_set_password(
- full_username, password, seed=seed)
-
- logger.debug('First Run Wizard Done.')
- cb = self.success_cb
- if cb and callable(cb):
- self.success_cb()
-
- # misc helpers
-
- def get_random_str(self, n):
- """
- returns a random string
- :param n: the length of the desired string
- :rvalue: str
- """
- from string import (ascii_uppercase, ascii_lowercase, digits)
- from random import choice
- return ''.join(choice(
- ascii_uppercase +
- ascii_lowercase +
- digits) for x in range(n))
-
- def set_providerconfig(self, providerconfig):
- """
- sets a providerconfig attribute
- used when we fetch and parse a json configuration
- """
- self.providerconfig = providerconfig
-
- def get_provider_by_index(self): # pragma: no cover
- """
- returns the value of a provider given its index.
- this was used in the select provider page,
- in the case where we were preseeding providers in a combobox
- """
- # Leaving it here for the moment when we go back at the
- # option of preseeding with known provider values.
- provider = self.field('provider_index')
- return self.providers[provider]
-
-
-if __name__ == '__main__':
- # standalone test
- # it can be (somehow) run against
- # gui/tests/integration/fake_user_signup.py
-
- import sys
- import logging
- logging.basicConfig()
- logger = logging.getLogger()
- logger.setLevel(logging.DEBUG)
-
- app = QtGui.QApplication(sys.argv)
- server = sys.argv[1] if len(sys.argv) > 1 else None
-
- trusted_certs = {
- "3DF83F316BFA0186"
- "0A11A5C9C7FC24B9"
- "18C62B941192CC1A"
- "49AE62218B2A4B7C": ['springbok']}
-
- wizard = FirstRunWizard(
- None, trusted_certs=trusted_certs,
- debug_server=server)
- wizard.show()
- sys.exit(app.exec_())
diff --git a/src/leap/gui/locale_rc.py b/src/leap/gui/locale_rc.py
deleted file mode 100644
index 8c383709..00000000
--- a/src/leap/gui/locale_rc.py
+++ /dev/null
@@ -1,813 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Resource object code
-#
-# Created: Fri Jan 25 18:19:04 2013
-# 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\x17\x94\
-\x3c\
-\xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\x42\
-\x00\x00\x01\x30\x00\x8f\x9b\xbe\x00\x00\x14\x69\x01\x23\x92\xe5\
-\x00\x00\x10\x2f\x01\x87\x64\x8e\x00\x00\x08\xbe\x01\xa8\xbe\x7e\
-\x00\x00\x0d\xf4\x02\x2c\xac\xe9\x00\x00\x0b\x9c\x02\x3a\xce\xbf\
-\x00\x00\x15\xe2\x02\x6e\x0f\xe5\x00\x00\x09\x2d\x02\x87\x60\x9e\
-\x00\x00\x06\xc6\x02\xaa\x52\x6e\x00\x00\x07\xc9\x02\xf2\xe0\x59\
-\x00\x00\x0a\x6c\x03\xec\x70\x0e\x00\x00\x10\x9c\x04\xd4\x45\xee\
-\x00\x00\x0d\x3c\x05\xb7\x8f\x59\x00\x00\x0c\x35\x06\x3e\x6a\x9e\
-\x00\x00\x06\x01\x06\x40\xa8\x7e\x00\x00\x0b\x02\x06\xee\xff\x6e\
-\x00\x00\x13\x50\x08\x13\xe8\xae\x00\x00\x0c\xc2\x08\x7a\x64\xee\
-\x00\x00\x11\x8b\x08\xe6\x98\x33\x00\x00\x05\x93\x08\xe6\x98\x33\
-\x00\x00\x0f\xb0\x09\x5c\x35\xe1\x00\x00\x0e\x96\x09\x74\x75\x4e\
-\x00\x00\x0d\x9c\x09\x98\x34\x0e\x00\x00\x12\x55\x09\xd8\x1f\x95\
-\x00\x00\x15\x19\x09\xfc\x2c\x8e\x00\x00\x05\x19\x09\xfe\x05\x90\
-\x00\x00\x0f\x06\x0a\x74\xb8\x1e\x00\x00\x00\xe6\x0a\xfd\x99\xfe\
-\x00\x00\x00\x6d\x0b\xd2\x4b\x3f\x00\x00\x07\x7d\x0c\x44\x41\xbe\
-\x00\x00\x00\x00\x0c\xc0\x94\x05\x00\x00\x09\xf2\x0d\x0d\x9d\xc5\
-\x00\x00\x06\x5f\x0d\x15\x34\x70\x00\x00\x09\x98\x0e\x36\x15\x54\
-\x00\x00\x08\x47\x0e\x7e\xf5\xee\x00\x00\x0f\x42\x0e\x91\x50\x3e\
-\x00\x00\x15\x76\x0e\xc0\xbb\x72\x00\x00\x12\xfb\x0f\x27\x0d\x6e\
-\x00\x00\x11\x22\x69\x00\x00\x16\x43\x03\x00\x00\x00\x3e\x00\x41\
-\x00\x73\x00\x73\x00\x69\x00\x73\x00\x74\x00\x65\x00\x6e\x00\x74\
-\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x65\x00\x72\x00\x73\
-\x00\x74\x00\x6d\x00\x61\x00\x6c\x00\x69\x00\x67\x00\x65\x00\x6e\
-\x00\x20\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x11\x46\x69\x72\x73\x74\x20\x72\x75\x6e\x20\
-\x77\x69\x7a\x61\x72\x64\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\
-\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x40\x00\x4d\x00\x69\x00\
-\x74\x00\x20\x00\x62\x00\x65\x00\x73\x00\x74\x00\x65\x00\x68\x00\
-\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x44\x00\x61\x00\
-\x74\x00\x65\x00\x6e\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x6c\x00\
-\x6f\x00\x67\x00\x67\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\
-\x06\x00\x00\x00\x1b\x4c\x6f\x67\x20\x49\x6e\x20\x77\x69\x74\x68\
-\x20\x6d\x79\x20\x63\x72\x65\x64\x65\x6e\x74\x69\x61\x6c\x73\x2e\
-\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\x65\x01\x03\
-\x00\x00\x02\xb8\x00\x57\x00\x69\x00\x72\x00\x20\x00\x77\x00\x65\
-\x00\x72\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x64\x00\x69\x00\x63\
-\x00\x68\x00\x20\x00\x6e\x00\x75\x00\x6e\x00\x20\x00\x64\x00\x75\
-\x00\x72\x00\x63\x00\x68\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x69\
-\x00\x67\x00\x65\x00\x20\x00\x4b\x00\x6f\x00\x6e\x00\x66\x00\x69\
-\x00\x67\x00\x75\x00\x72\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\
-\x00\x65\x00\x6e\x00\x20\x00\x66\x00\xfc\x00\x68\x00\x72\x00\x65\
-\x00\x6e\x00\x2c\x00\x20\x00\x64\x00\x69\x00\x65\x00\x20\x00\x64\
-\x00\x75\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x64\x00\x65\
-\x00\x6e\x00\x20\x00\x65\x00\x72\x00\x73\x00\x74\x00\x65\x00\x6e\
-\x00\x20\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x20\x00\x62\
-\x00\x65\x00\x6e\x00\xf6\x00\x74\x00\x69\x00\x67\x00\x73\x00\x74\
-\x00\x2e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\
-\x00\x3e\x00\x57\x00\x65\x00\x6e\x00\x6e\x00\x20\x00\x64\x00\x75\
-\x00\x20\x00\x64\x00\x69\x00\x65\x00\x73\x00\x65\x00\x20\x00\x4b\
-\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\
-\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x6a\
-\x00\x65\x00\x6d\x00\x61\x00\x6c\x00\x73\x00\x20\x00\xe4\x00\x6e\
-\x00\x64\x00\x65\x00\x72\x00\x6e\x00\x20\x00\x6d\x00\x75\x00\x73\
-\x00\x73\x00\x74\x00\x2c\x00\x20\x00\x66\x00\x69\x00\x6e\x00\x64\
-\x00\x65\x00\x73\x00\x74\x00\x20\x00\x64\x00\x75\x00\x20\x00\x64\
-\x00\x65\x00\x6e\x00\x20\x00\x41\x00\x73\x00\x73\x00\x69\x00\x73\
-\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x69\
-\x00\x6d\x00\x20\x00\x27\x00\x3c\x00\x69\x00\x3e\x00\x45\x00\x69\
-\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x6c\x00\x6c\x00\x75\x00\x6e\
-\x00\x67\x00\x65\x00\x6e\x00\x3c\x00\x2f\x00\x69\x00\x3e\x00\x27\
-\x00\x2d\x00\x4d\x00\x65\x00\x6e\x00\xfc\x00\x20\x00\x64\x00\x65\
-\x00\x73\x00\x20\x00\x48\x00\x61\x00\x75\x00\x70\x00\x66\x00\x65\
-\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x72\x00\x73\x00\x2e\x00\x3c\
-\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x4d\
-\x00\xf6\x00\x63\x00\x68\x00\x74\x00\x65\x00\x73\x00\x74\x00\x20\
-\x00\x64\x00\x75\x00\x20\x00\x64\x00\x69\x00\x63\x00\x68\x00\x20\
-\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x65\
-\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\x75\x00\x65\x00\x6e\x00\x20\
-\x00\x41\x00\x63\x00\x63\x00\x6f\x00\x75\x00\x6e\x00\x74\x00\x20\
-\x00\x3c\x00\x62\x00\x3e\x00\x61\x00\x6e\x00\x6d\x00\x65\x00\x6c\
-\x00\x64\x00\x65\x00\x6e\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\
-\x00\x6f\x00\x64\x00\x65\x00\x72\x00\x20\x00\x6d\x00\x69\x00\x74\
-\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x6d\x00\x20\x00\x62\
-\x00\x65\x00\x73\x00\x74\x00\x65\x00\x68\x00\x65\x00\x6e\x00\x64\
-\x00\x65\x00\x6e\x00\x20\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\
-\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\x20\x00\x3c\x00\x62\x00\x3e\
-\x00\x65\x00\x69\x00\x6e\x00\x6c\x00\x6f\x00\x67\x00\x67\x00\x65\
-\x00\x6e\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x3f\x08\x00\x00\x00\
-\x00\x06\x00\x00\x01\x5d\x4e\x6f\x77\x20\x77\x65\x20\x77\x69\x6c\
-\x6c\x20\x67\x75\x69\x64\x65\x20\x79\x6f\x75\x20\x74\x68\x72\x6f\
-\x75\x67\x68\x20\x73\x6f\x6d\x65\x20\x63\x6f\x6e\x66\x69\x67\x75\
-\x72\x61\x74\x69\x6f\x6e\x20\x74\x68\x61\x74\x20\x69\x73\x20\x6e\
-\x65\x65\x64\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x79\x6f\x75\
-\x20\x63\x61\x6e\x20\x63\x6f\x6e\x6e\x65\x63\x74\x20\x66\x6f\x72\
-\x20\x74\x68\x65\x20\x66\x69\x72\x73\x74\x20\x74\x69\x6d\x65\x2e\
-\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x49\x66\x20\x79\x6f\x75\x20\x65\
-\x76\x65\x72\x20\x6e\x65\x65\x64\x20\x74\x6f\x20\x6d\x6f\x64\x69\
-\x66\x79\x20\x74\x68\x65\x73\x65\x20\x6f\x70\x74\x69\x6f\x6e\x73\
-\x20\x61\x67\x61\x69\x6e\x2c\x20\x79\x6f\x75\x20\x63\x61\x6e\x20\
-\x66\x69\x6e\x64\x20\x74\x68\x65\x20\x77\x69\x7a\x61\x72\x64\x20\
-\x69\x6e\x20\x74\x68\x65\x20\x27\x3c\x69\x3e\x53\x65\x74\x74\x69\
-\x6e\x67\x73\x3c\x2f\x69\x3e\x27\x20\x6d\x65\x6e\x75\x20\x66\x72\
-\x6f\x6d\x20\x74\x68\x65\x20\x6d\x61\x69\x6e\x20\x77\x69\x6e\x64\
-\x6f\x77\x2e\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x44\x6f\x20\x79\x6f\
-\x75\x20\x77\x61\x6e\x74\x20\x74\x6f\x20\x3c\x62\x3e\x73\x69\x67\
-\x6e\x20\x75\x70\x3c\x2f\x62\x3e\x20\x66\x6f\x72\x20\x61\x20\x6e\
-\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\x2c\x20\x6f\x72\x20\x3c\
-\x62\x3e\x6c\x6f\x67\x20\x69\x6e\x3c\x2f\x62\x3e\x20\x77\x69\x74\
-\x68\x20\x61\x6e\x20\x61\x6c\x72\x65\x61\x64\x79\x20\x65\x78\x69\
-\x73\x74\x69\x6e\x67\x20\x75\x73\x65\x72\x6e\x61\x6d\x65\x3f\x3c\
-\x62\x72\x3e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x42\x00\x46\x00\xfc\x00\x72\x00\x20\x00\
-\x65\x00\x69\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\
-\x75\x00\x65\x00\x6e\x00\x20\x00\x41\x00\x63\x00\x63\x00\x6f\x00\
-\x75\x00\x6e\x00\x74\x00\x20\x00\x61\x00\x6e\x00\x6d\x00\x65\x00\
-\x6c\x00\x64\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x1a\x53\x69\x67\x6e\x20\x75\x70\x20\x66\x6f\x72\x20\x61\
-\x20\x6e\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\x2e\x07\x00\x00\
-\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\
-\x38\x00\x41\x00\x75\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x74\x00\
-\x69\x00\x66\x00\x69\x00\x7a\x00\x69\x00\x65\x00\x72\x00\x75\x00\
-\x6e\x00\x67\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\
-\x72\x00\x3a\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x69\x6f\
-\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\x00\x00\x09\
-\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2a\x00\
-\x41\x00\x6e\x00\x6d\x00\x65\x00\x6c\x00\x64\x00\x65\x00\x64\x00\
-\x61\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x6b\x00\x6f\x00\x72\x00\
-\x72\x00\x65\x00\x6b\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x16\x43\x72\x65\x64\x65\x6e\x74\x69\x61\x6c\x73\x20\x76\
-\x61\x6c\x69\x64\x61\x74\x65\x64\x2e\x07\x00\x00\x00\x09\x4c\x6f\
-\x67\x49\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x41\x00\
-\x75\x00\x66\x00\x6c\x00\xf6\x00\x73\x00\x65\x00\x6e\x00\x20\x00\
-\x64\x00\x65\x00\x73\x00\x20\x00\x44\x00\x6f\x00\x6d\x00\x61\x00\
-\x69\x00\x6e\x00\x2d\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\
-\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x52\x65\x73\x6f\x6c\
-\x76\x69\x6e\x67\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6e\x61\x6d\x65\
-\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x6a\x00\x44\x00\x65\x00\x72\x00\x20\x00\x55\x00\x73\
-\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\x00\x20\x00\x6d\
-\x00\x75\x00\x73\x00\x73\x00\x20\x00\x69\x00\x6e\x00\x20\x00\x64\
-\x00\x65\x00\x72\x00\x20\x00\x46\x00\x6f\x00\x72\x00\x6d\x00\x20\
-\x00\x75\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\
-\x00\x40\x00\x70\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\
-\x00\x72\x00\x20\x00\x73\x00\x65\x00\x69\x00\x6e\x00\x2e\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x2f\x55\x73\x65\x72\x6e\x61\x6d\x65\
-\x20\x6d\x75\x73\x74\x20\x62\x65\x20\x69\x6e\x20\x74\x68\x65\x20\
-\x75\x73\x65\x72\x6e\x61\x6d\x65\x40\x70\x72\x6f\x76\x69\x64\x65\
-\x72\x20\x66\x6f\x72\x6d\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\
-\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x1a\x00\x50\x00\x72\x00\
-\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x69\x00\
-\x6e\x00\x66\x00\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x20\x49\x6e\x66\x6f\x07\x00\x00\x00\
-\x10\x50\x72\x6f\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x3e\x00\x44\x00\x61\x00\x73\x00\x20\x00\
-\x69\x00\x73\x00\x74\x00\x2c\x00\x20\x00\x77\x00\x61\x00\x73\x00\
-\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x50\x00\x72\x00\x6f\x00\
-\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\x73\x00\x61\x00\
-\x67\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x54\
-\x68\x69\x73\x20\x69\x73\x20\x77\x68\x61\x74\x20\x70\x72\x6f\x76\
-\x69\x64\x65\x72\x20\x73\x61\x79\x73\x2e\x07\x00\x00\x00\x10\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\
-\x03\x00\x00\x00\x30\x00\xdc\x00\x62\x00\x65\x00\x72\x00\x70\x00\
-\x72\x00\xfc\x00\x66\x00\x65\x00\x20\x00\x43\x00\x41\x00\x2d\x00\
-\x46\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x72\x00\x70\x00\x72\x00\
-\x69\x00\x6e\x00\x74\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x43\
-\x68\x65\x63\x6b\x69\x6e\x67\x20\x43\x41\x20\x66\x69\x6e\x67\x65\
-\x72\x70\x72\x69\x6e\x74\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\
-\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\
-\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2e\x00\x46\x00\xfc\
-\x00\x68\x00\x72\x00\x65\x00\x20\x00\x61\x00\x75\x00\x74\x00\x6f\
-\x00\x63\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x20\x00\x64\
-\x00\x75\x00\x72\x00\x63\x00\x68\x00\x2e\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x11\x44\x6f\x69\x6e\x67\x20\x61\x75\x74\x6f\x63\x6f\
-\x6e\x66\x69\x67\x2e\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\
-\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\
-\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\x00\x48\x00\x6f\x00\
-\x6c\x00\x65\x00\x20\x00\x43\x00\x41\x00\x2d\x00\x5a\x00\x65\x00\
-\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\x61\x00\x74\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00\x17\x46\x65\x74\x63\x68\x69\x6e\
-\x67\x20\x43\x41\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\
-\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\
-\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\
-\x01\x03\x00\x00\x00\x1c\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\
-\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x53\x00\x65\x00\x74\x00\x75\
-\x00\x70\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\x50\x72\x6f\x76\
-\x69\x64\x65\x72\x20\x73\x65\x74\x75\x70\x07\x00\x00\x00\x1b\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\
-\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x30\
-\x00\xdc\x00\x62\x00\x65\x00\x72\x00\x70\x00\x72\x00\xfc\x00\x66\
-\x00\x65\x00\x20\x00\x41\x00\x50\x00\x49\x00\x2d\x00\x5a\x00\x65\
-\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\x61\x00\x74\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1a\x56\x61\x6c\x69\x64\x61\
-\x74\x69\x6e\x67\x20\x61\x70\x69\x20\x63\x65\x72\x74\x69\x66\x69\
-\x63\x61\x74\x65\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\
-\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x50\x00\x4b\x00\x6f\x00\x6e\
-\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\
-\x00\x74\x00\x20\x00\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\
-\x00\x72\x00\x69\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x20\x00\x28\
-\x00\x62\x00\x61\x00\x64\x00\x20\x00\x72\x00\x65\x00\x73\x00\x70\
-\x00\x6f\x00\x6e\x00\x73\x00\x65\x00\x29\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x72\x65\
-\x67\x69\x73\x74\x65\x72\x20\x28\x62\x61\x64\x20\x72\x65\x73\x70\
-\x6f\x6e\x73\x65\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4e\
-\x00\x56\x00\x65\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\
-\x00\x6e\x00\x67\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\
-\x00\x72\x00\x20\x00\x7a\x00\x75\x00\x20\x00\x50\x00\x72\x00\x6f\
-\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\x28\x00\x63\
-\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x72\x00\x72\x00\x29\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x27\x45\x72\x72\x6f\x72\x20\x43\x6f\
-\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\x6f\x20\x70\x72\x6f\x76\
-\x69\x64\x65\x72\x20\x28\x63\x6f\x6e\x6e\x65\x72\x72\x29\x2e\x07\
-\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4e\x00\x56\x00\x65\x00\x72\
-\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\x67\x00\x73\
-\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\x20\x00\x7a\
-\x00\x75\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\
-\x00\x65\x00\x72\x00\x20\x00\x28\x00\x74\x00\x69\x00\x6d\x00\x65\
-\x00\x6f\x00\x75\x00\x74\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\
-\x00\x26\x45\x72\x72\x6f\x72\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\
-\x6e\x67\x20\x74\x6f\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\
-\x74\x69\x6d\x65\x6f\x75\x74\x29\x07\x00\x00\x00\x10\x52\x65\x67\
-\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\
-\x00\x00\x4a\x00\x46\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\
-\x20\x00\x77\x00\xe4\x00\x68\x00\x72\x00\x65\x00\x6e\x00\x64\x00\
-\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x52\x00\x65\x00\x67\x00\
-\x69\x00\x73\x00\x74\x00\x72\x00\x69\x00\x65\x00\x72\x00\x75\x00\
-\x6e\x00\x67\x00\x20\x00\x28\x00\x25\x00\x73\x00\x29\x08\x00\x00\
-\x00\x00\x06\x00\x00\x00\x1e\x45\x72\x72\x6f\x72\x20\x64\x75\x72\
-\x69\x6e\x67\x20\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\
-\x20\x28\x25\x73\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\
-\x00\x50\x00\x61\x00\x73\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\
-\x00\x20\x00\x73\x00\x74\x00\x69\x00\x6d\x00\x6d\x00\x74\x00\x20\
-\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\xfc\x00\x62\
-\x00\x65\x00\x72\x00\x69\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x19\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x64\
-\x6f\x65\x73\x20\x6e\x6f\x74\x20\x6d\x61\x74\x63\x68\x2e\x2e\x07\
-\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x26\x00\x50\x00\x61\x00\x73\
-\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\x00\x20\x00\x7a\x00\x75\
-\x00\x20\x00\x73\x00\x69\x00\x6d\x00\x70\x00\x65\x00\x6c\x00\x2e\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x50\x61\x73\x73\x77\x6f\
-\x72\x64\x20\x74\x6f\x6f\x20\x6f\x62\x76\x69\x6f\x75\x73\x2e\x07\
-\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x20\x00\x50\x00\x61\x00\x73\
-\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\x00\x20\x00\x7a\x00\x75\
-\x00\x20\x00\x6b\x00\x75\x00\x72\x00\x7a\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x13\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x74\x6f\x6f\
-\x20\x73\x68\x6f\x72\x74\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\
-\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\
-\x00\x58\x00\x52\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\
-\x00\x69\x00\x65\x00\x72\x00\x65\x00\x20\x00\x65\x00\x69\x00\x6e\
-\x00\x65\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\x75\x00\x65\x00\x6e\
-\x00\x20\x00\x55\x00\x73\x00\x65\x00\x72\x00\x20\x00\x62\x00\x65\
-\x00\x69\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\
-\x00\x65\x00\x72\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x25\x52\x65\x67\x69\x73\x74\x65\x72\x20\x61\x20\x6e\
-\x65\x77\x20\x75\x73\x65\x72\x20\x77\x69\x74\x68\x20\x70\x72\x6f\
-\x76\x69\x64\x65\x72\x20\x25\x73\x2e\x07\x00\x00\x00\x10\x52\x65\
-\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x34\x00\x52\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\
-\x00\x72\x00\x69\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x67\x00\x20\
-\x00\x65\x00\x72\x00\x66\x00\x6f\x00\x6c\x00\x67\x00\x72\x00\x65\
-\x00\x69\x00\x63\x00\x68\x00\x21\x08\x00\x00\x00\x00\x06\x00\x00\
-\x00\x17\x52\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x20\x73\
-\x75\x63\x63\x65\x65\x64\x65\x64\x21\x07\x00\x00\x00\x10\x52\x65\
-\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x10\x00\x41\x00\x6e\x00\x6d\x00\x65\x00\x6c\x00\x64\
-\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x53\x69\
-\x67\x6e\x20\x55\x70\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x32\
-\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\
-\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\x76\
-\x00\x65\x00\x72\x00\x66\x00\xfc\x00\x67\x00\x62\x00\x61\x00\x72\
-\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x55\x73\x65\x72\
-\x6e\x61\x6d\x65\x20\x6e\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\
-\x6c\x65\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\
-\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x38\x00\x41\
-\x00\x75\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x66\
-\x00\x69\x00\x7a\x00\x69\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x67\
-\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\x3a\
-\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\
-\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x69\x6f\x6e\x20\x65\
-\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\x00\x00\x1a\x52\x65\x67\
-\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\
-\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x26\x00\x48\x00\
-\x6f\x00\x6c\x00\x65\x00\x20\x00\x45\x00\x49\x00\x50\x00\x2d\x00\
-\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\
-\x61\x00\x74\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\x46\x65\x74\
-\x63\x68\x69\x6e\x67\x20\x65\x69\x70\x20\x63\x65\x72\x74\x69\x66\
-\x69\x63\x61\x74\x65\x07\x00\x00\x00\x1a\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\x00\x48\x00\x6f\x00\x6c\
-\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\
-\x00\x65\x00\x72\x00\x2d\x00\x4b\x00\x6f\x00\x6e\x00\x66\x00\x69\
-\x00\x67\x00\x75\x00\x72\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\
-\x00\x2e\x00\x2e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\
-\x46\x65\x74\x63\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\
-\x72\x20\x63\x6f\x6e\x66\x69\x67\x2e\x2e\x2e\x07\x00\x00\x00\x1a\
-\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\
-\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2c\
-\x00\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\
-\x00\x61\x00\x74\x00\x73\x00\xfc\x00\x62\x00\x65\x00\x72\x00\x70\
-\x00\x72\x00\xfc\x00\x66\x00\x75\x00\x6e\x00\x67\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x16\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\
-\x65\x20\x76\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x07\x00\x00\x00\
-\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\
-\x61\x67\x65\x01\x03\x00\x00\x00\x72\x00\x4b\x00\x6f\x00\x6e\x00\
-\x6e\x00\x74\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\
-\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x49\x00\x6e\x00\x66\x00\
-\x6f\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\
-\x68\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x74\x00\x65\x00\x72\x00\
-\x6c\x00\x61\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x28\x00\x72\x00\
-\x65\x00\x66\x00\x75\x00\x73\x00\x65\x00\x64\x00\x20\x00\x63\x00\
-\x6f\x00\x6e\x00\x6e\x00\x2e\x00\x29\x00\x2e\x08\x00\x00\x00\x00\
-\x06\x00\x00\x00\x31\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x64\
-\x6f\x77\x6e\x6c\x6f\x61\x64\x20\x70\x72\x6f\x76\x69\x64\x65\x72\
-\x20\x69\x6e\x66\x6f\x20\x28\x72\x65\x66\x75\x73\x65\x64\x20\x63\
-\x6f\x6e\x6e\x2e\x29\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\
-\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\
-\x00\x00\x5e\x00\x4b\x00\x6f\x00\x6e\x00\x6e\x00\x74\x00\x65\x00\
-\x20\x00\x6b\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\x49\x00\
-\x6e\x00\x66\x00\x6f\x00\x72\x00\x6d\x00\x61\x00\x74\x00\x69\x00\
-\x6f\x00\x6e\x00\x20\x00\x76\x00\x6f\x00\x6d\x00\x20\x00\x50\x00\
-\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\
-\x62\x00\x65\x00\x6b\x00\x6f\x00\x6d\x00\x6d\x00\x65\x00\x6e\x00\
-\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\
-\x20\x6e\x6f\x74\x20\x67\x65\x74\x20\x69\x6e\x66\x6f\x20\x66\x72\
-\x6f\x6d\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x2e\x07\x00\x00\x00\
-\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\
-\x61\x67\x65\x01\x03\x00\x00\x00\x20\x00\x47\x00\x69\x00\x62\x00\
-\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\
-\x72\x00\x20\x00\x65\x00\x69\x00\x6e\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x0e\x45\x6e\x74\x65\x72\x20\x50\x72\x6f\x76\x69\x64\x65\
-\x72\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\
-\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\xa6\x00\x42\
-\x00\x69\x00\x74\x00\x74\x00\x65\x00\x20\x00\x67\x00\x69\x00\x62\
-\x00\x20\x00\x64\x00\x69\x00\x65\x00\x20\x00\x44\x00\x6f\x00\x6d\
-\x00\x61\x00\x69\x00\x6e\x00\x20\x00\x64\x00\x65\x00\x73\x00\x20\
-\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\
-\x00\x73\x00\x20\x00\x61\x00\x6e\x00\x2c\x00\x20\x00\x64\x00\x65\
-\x00\x6e\x00\x20\x00\x64\x00\x75\x00\x20\x00\x66\x00\xfc\x00\x72\
-\x00\x20\x00\x64\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\x56\
-\x00\x65\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\
-\x00\x67\x00\x20\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\x65\x00\x6e\
-\x00\x20\x00\x6d\x00\xf6\x00\x63\x00\x68\x00\x74\x00\x65\x00\x73\
-\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x4c\x50\x6c\
-\x65\x61\x73\x65\x20\x65\x6e\x74\x65\x72\x20\x74\x68\x65\x20\x64\
-\x6f\x6d\x61\x69\x6e\x20\x6f\x66\x20\x74\x68\x65\x20\x70\x72\x6f\
-\x76\x69\x64\x65\x72\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\x20\x74\
-\x6f\x20\x75\x73\x65\x20\x66\x6f\x72\x20\x79\x6f\x75\x72\x20\x63\
-\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x2e\x07\x00\x00\x00\x12\x53\
-\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x60\x00\x53\x00\x65\x00\x72\x00\x76\x00\
-\x65\x00\x72\x00\x2d\x00\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\
-\x66\x00\x69\x00\x6b\x00\x61\x00\x74\x00\x20\x00\x6b\x00\x6f\x00\
-\x6e\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x6e\x00\x69\x00\x63\x00\
-\x68\x00\x74\x00\x20\x00\x62\x00\x65\x00\x73\x00\x74\x00\xe4\x00\
-\x74\x00\x69\x00\x67\x00\x74\x00\x20\x00\x77\x00\x65\x00\x72\x00\
-\x64\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\
-\x29\x53\x65\x72\x76\x65\x72\x20\x63\x65\x72\x74\x69\x66\x69\x63\
-\x61\x74\x65\x20\x63\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x62\x65\
-\x20\x76\x65\x72\x69\x66\x69\x65\x64\x2e\x07\x00\x00\x00\x12\x53\
-\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x22\x00\x50\x00\x72\x00\xfc\x00\x66\x00\
-\x65\x00\x20\x00\x44\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\
-\x2d\x00\x4e\x00\x61\x00\x6d\x00\x65\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x14\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x64\x6f\x6d\x61\
-\x69\x6e\x20\x6e\x61\x6d\x65\x07\x00\x00\x00\x12\x53\x65\x6c\x65\
-\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x2c\x00\x50\x00\x72\x00\xfc\x00\x66\x00\x65\x00\x20\
-\x00\x48\x00\x54\x00\x54\x00\x50\x00\x53\x00\x2d\x00\x56\x00\x65\
-\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\x67\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x19\x63\x68\x65\x63\x6b\x69\
-\x6e\x67\x20\x68\x74\x74\x70\x73\x20\x63\x6f\x6e\x6e\x65\x63\x74\
-\x69\x6f\x6e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\
-\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\
-\x00\x48\x00\x6f\x00\x6c\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\
-\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x49\x00\x6e\
-\x00\x66\x00\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x66\x65\
-\x74\x63\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\
-\x69\x6e\x66\x6f\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x88\x00\x00\x00\
-\x02\x01\x01\
-\x00\x00\x18\x32\
-\x3c\
-\xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\x42\
-\x00\x00\x01\x38\x00\x8f\x9b\xbe\x00\x00\x14\x83\x01\x23\x92\xe5\
-\x00\x00\x10\x3d\x01\x87\x64\x8e\x00\x00\x08\x7a\x01\xa8\xbe\x7e\
-\x00\x00\x0e\x02\x02\x2c\xac\xe9\x00\x00\x0b\x8a\x02\x3a\xce\xbf\
-\x00\x00\x16\x62\x02\x6e\x0f\xe5\x00\x00\x08\xdd\x02\x87\x60\x9e\
-\x00\x00\x06\x6e\x02\xaa\x52\x6e\x00\x00\x07\x6b\x02\xf2\xe0\x59\
-\x00\x00\x0a\x5e\x03\xec\x70\x0e\x00\x00\x10\xb8\x04\xd4\x45\xee\
-\x00\x00\x0d\x24\x05\xb7\x8f\x59\x00\x00\x0c\x27\x06\x3e\x6a\x9e\
-\x00\x00\x05\x9f\x06\x40\xa8\x7e\x00\x00\x0a\xea\x06\xee\xff\x6e\
-\x00\x00\x13\x74\x08\x13\xe8\xae\x00\x00\x0c\xa6\x08\x7a\x64\xee\
-\x00\x00\x11\xc5\x08\xe6\x98\x33\x00\x00\x05\x35\x08\xe6\x98\x33\
-\x00\x00\x0f\xc2\x09\x5c\x35\xe1\x00\x00\x0e\xaa\x09\x74\x75\x4e\
-\x00\x00\x0d\x94\x09\x98\x34\x0e\x00\x00\x12\x89\x09\xd8\x1f\x95\
-\x00\x00\x15\x79\x09\xeb\x5c\xb1\x00\x00\x15\x35\x09\xfc\x2c\x8e\
-\x00\x00\x04\xc7\x09\xfe\x05\x90\x00\x00\x0f\x16\x0a\x74\xb8\x1e\
-\x00\x00\x00\xd6\x0a\xfd\x99\xfe\x00\x00\x00\x51\x0b\xd2\x4b\x3f\
-\x00\x00\x07\x15\x0c\x44\x41\xbe\x00\x00\x00\x00\x0c\xc0\x94\x05\
-\x00\x00\x09\xd6\x0d\x0d\x9d\xc5\x00\x00\x06\x01\x0d\x15\x34\x70\
-\x00\x00\x09\x62\x0e\x36\x15\x54\x00\x00\x07\xed\x0e\x7e\xf5\xee\
-\x00\x00\x0f\x5a\x0e\x91\x50\x3e\x00\x00\x15\xee\x0e\xc0\xbb\x72\
-\x00\x00\x13\x1b\x0f\x27\x0d\x6e\x00\x00\x11\x54\x69\x00\x00\x16\
-\xd9\x03\x00\x00\x00\x22\x00\x50\x00\x72\x00\x69\x00\x6d\x00\x65\
-\x00\x72\x00\x61\x00\x20\x00\x43\x00\x6f\x00\x6e\x00\x65\x00\x78\
-\x00\x69\x00\x6f\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\
-\x00\x11\x46\x69\x72\x73\x74\x20\x72\x75\x6e\x20\x77\x69\x7a\x61\
-\x72\x64\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x4c\x00\x4c\x00\x6f\x00\x67\x00\x75\x00\
-\x65\x00\x61\x00\x72\x00\x6d\x00\x65\x00\x20\x00\x63\x00\x6f\x00\
-\x6e\x00\x20\x00\x75\x00\x6e\x00\x20\x00\x75\x00\x73\x00\x75\x00\
-\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x71\x00\x75\x00\x65\x00\
-\x20\x00\x79\x00\x61\x00\x20\x00\x74\x00\x65\x00\x6e\x00\x67\x00\
-\x6f\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x4c\x6f\x67\
-\x20\x49\x6e\x20\x77\x69\x74\x68\x20\x6d\x79\x20\x63\x72\x65\x64\
-\x65\x6e\x74\x69\x61\x6c\x73\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\
-\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x02\x76\x00\x56\x00\x61\
-\x00\x6d\x00\x6f\x00\x73\x00\x20\x00\x61\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x72\
-\x00\x20\x00\x61\x00\x6c\x00\x67\x00\x75\x00\x6e\x00\x61\x00\x73\
-\x00\x20\x00\x63\x00\x6f\x00\x73\x00\x61\x00\x73\x00\x20\x00\x61\
-\x00\x6e\x00\x74\x00\x65\x00\x73\x00\x20\x00\x64\x00\x65\x00\x20\
-\x00\x71\x00\x75\x00\x65\x00\x20\x00\x74\x00\x65\x00\x20\x00\x70\
-\x00\x75\x00\x65\x00\x64\x00\x61\x00\x73\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x72\x00\x20\x00\x70\
-\x00\x6f\x00\x72\x00\x20\x00\x70\x00\x72\x00\x69\x00\x6d\x00\x65\
-\x00\x72\x00\x61\x00\x20\x00\x76\x00\x65\x00\x7a\x00\x2e\x00\x3c\
-\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x53\
-\x00\x69\x00\x20\x00\x6e\x00\x65\x00\x63\x00\x65\x00\x73\x00\x69\
-\x00\x74\x00\x61\x00\x73\x00\x20\x00\x6d\x00\x6f\x00\x64\x00\x69\
-\x00\x66\x00\x69\x00\x63\x00\x61\x00\x72\x00\x20\x00\x65\x00\x73\
-\x00\x74\x00\x61\x00\x73\x00\x20\x00\x6f\x00\x70\x00\x63\x00\x69\
-\x00\x6f\x00\x6e\x00\x65\x00\x73\x00\x20\x00\x64\x00\x65\x00\x20\
-\x00\x6e\x00\x75\x00\x65\x00\x76\x00\x6f\x00\x2c\x00\x20\x00\x70\
-\x00\x75\x00\x65\x00\x64\x00\x65\x00\x73\x00\x20\x00\x65\x00\x6e\
-\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\
-\x00\x65\x00\x73\x00\x74\x00\x65\x00\x20\x00\x61\x00\x73\x00\x69\
-\x00\x73\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x65\
-\x00\x6e\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x6d\x00\x65\x00\x6e\
-\x00\x75\x00\x20\x00\x64\x00\x65\x00\x20\x00\x27\x00\x3c\x00\x69\
-\x00\x3e\x00\x4f\x00\x70\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x65\
-\x00\x73\x00\x3c\x00\x2f\x00\x69\x00\x3e\x00\x27\x00\x20\x00\x65\
-\x00\x6e\x00\x20\x00\x6c\x00\x61\x00\x20\x00\x76\x00\x65\x00\x6e\
-\x00\x74\x00\x61\x00\x6e\x00\x61\x00\x20\x00\x70\x00\x72\x00\x69\
-\x00\x6e\x00\x63\x00\x69\x00\x70\x00\x61\x00\x6c\x00\x2e\x00\x3c\
-\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x51\
-\x00\x75\x00\x69\x00\x65\x00\x72\x00\x65\x00\x73\x00\x20\x00\x3c\
-\x00\x62\x00\x3e\x00\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\
-\x00\x72\x00\x61\x00\x72\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\
-\x00\x75\x00\x6e\x00\x61\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\
-\x00\x61\x00\x20\x00\x63\x00\x75\x00\x65\x00\x6e\x00\x74\x00\x61\
-\x00\x2c\x00\x20\x00\x6f\x00\x20\x00\x3c\x00\x62\x00\x3e\x00\x6c\
-\x00\x6f\x00\x67\x00\x75\x00\x65\x00\x61\x00\x72\x00\x74\x00\x65\
-\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\x00\x63\x00\x6f\x00\x6e\
-\x00\x20\x00\x74\x00\x75\x00\x20\x00\x75\x00\x73\x00\x75\x00\x61\
-\x00\x72\x00\x69\x00\x6f\x00\x3f\x00\x3c\x00\x62\x00\x72\x00\x3e\
-\x00\x20\x08\x00\x00\x00\x00\x06\x00\x00\x01\x5d\x4e\x6f\x77\x20\
-\x77\x65\x20\x77\x69\x6c\x6c\x20\x67\x75\x69\x64\x65\x20\x79\x6f\
-\x75\x20\x74\x68\x72\x6f\x75\x67\x68\x20\x73\x6f\x6d\x65\x20\x63\
-\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x20\x74\x68\x61\
-\x74\x20\x69\x73\x20\x6e\x65\x65\x64\x65\x64\x20\x62\x65\x66\x6f\
-\x72\x65\x20\x79\x6f\x75\x20\x63\x61\x6e\x20\x63\x6f\x6e\x6e\x65\
-\x63\x74\x20\x66\x6f\x72\x20\x74\x68\x65\x20\x66\x69\x72\x73\x74\
-\x20\x74\x69\x6d\x65\x2e\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x49\x66\
-\x20\x79\x6f\x75\x20\x65\x76\x65\x72\x20\x6e\x65\x65\x64\x20\x74\
-\x6f\x20\x6d\x6f\x64\x69\x66\x79\x20\x74\x68\x65\x73\x65\x20\x6f\
-\x70\x74\x69\x6f\x6e\x73\x20\x61\x67\x61\x69\x6e\x2c\x20\x79\x6f\
-\x75\x20\x63\x61\x6e\x20\x66\x69\x6e\x64\x20\x74\x68\x65\x20\x77\
-\x69\x7a\x61\x72\x64\x20\x69\x6e\x20\x74\x68\x65\x20\x27\x3c\x69\
-\x3e\x53\x65\x74\x74\x69\x6e\x67\x73\x3c\x2f\x69\x3e\x27\x20\x6d\
-\x65\x6e\x75\x20\x66\x72\x6f\x6d\x20\x74\x68\x65\x20\x6d\x61\x69\
-\x6e\x20\x77\x69\x6e\x64\x6f\x77\x2e\x3c\x62\x72\x3e\x3c\x62\x72\
-\x3e\x44\x6f\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\x20\x74\x6f\x20\
-\x3c\x62\x3e\x73\x69\x67\x6e\x20\x75\x70\x3c\x2f\x62\x3e\x20\x66\
-\x6f\x72\x20\x61\x20\x6e\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\
-\x2c\x20\x6f\x72\x20\x3c\x62\x3e\x6c\x6f\x67\x20\x69\x6e\x3c\x2f\
-\x62\x3e\x20\x77\x69\x74\x68\x20\x61\x6e\x20\x61\x6c\x72\x65\x61\
-\x64\x79\x20\x65\x78\x69\x73\x74\x69\x6e\x67\x20\x75\x73\x65\x72\
-\x6e\x61\x6d\x65\x3f\x3c\x62\x72\x3e\x07\x00\x00\x00\x09\x49\x6e\
-\x74\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x52\x00\
-\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\
-\x20\x00\x75\x00\x6e\x00\x61\x00\x20\x00\x63\x00\x75\x00\x65\x00\
-\x6e\x00\x74\x00\x61\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\x00\
-\x61\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1a\x53\x69\x67\
-\x6e\x20\x75\x70\x20\x66\x6f\x72\x20\x61\x20\x6e\x65\x77\x20\x61\
-\x63\x63\x6f\x75\x6e\x74\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\
-\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x45\x00\x72\x00\
-\x72\x00\x6f\x00\x72\x00\x20\x00\x64\x00\x65\x00\x20\x00\x61\x00\
-\x75\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x61\x00\
-\x63\x00\x69\x00\x6f\x00\x6e\x00\x3a\x00\x20\x00\x25\x00\x73\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\
-\x69\x63\x61\x74\x69\x6f\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\
-\x73\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\
-\x03\x00\x00\x00\x2e\x00\x43\x00\x72\x00\x65\x00\x64\x00\x65\x00\
-\x6e\x00\x63\x00\x69\x00\x61\x00\x6c\x00\x65\x00\x73\x00\x20\x00\
-\x76\x00\x61\x00\x6c\x00\x69\x00\x64\x00\x61\x00\x64\x00\x61\x00\
-\x73\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x43\x72\x65\
-\x64\x65\x6e\x74\x69\x61\x6c\x73\x20\x76\x61\x6c\x69\x64\x61\x74\
-\x65\x64\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\
-\x65\x01\x03\x00\x00\x00\x3a\x00\x52\x00\x65\x00\x73\x00\x6f\x00\
-\x6c\x00\x76\x00\x69\x00\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\
-\x6e\x00\x6f\x00\x6d\x00\x62\x00\x72\x00\x65\x00\x20\x00\x64\x00\
-\x65\x00\x20\x00\x64\x00\x6f\x00\x6d\x00\x69\x00\x6e\x00\x69\x00\
-\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x52\x65\x73\x6f\x6c\
-\x76\x69\x6e\x67\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6e\x61\x6d\x65\
-\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x5a\x00\x45\x00\x6c\x00\x20\x00\x75\x00\x73\x00\x75\
-\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x74\x00\x69\x00\x65\
-\x00\x6e\x00\x65\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\x73\
-\x00\x65\x00\x72\x00\x20\x00\x75\x00\x73\x00\x75\x00\x61\x00\x72\
-\x00\x69\x00\x6f\x00\x40\x00\x74\x00\x75\x00\x2e\x00\x70\x00\x72\
-\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x2f\x55\x73\x65\x72\x6e\x61\x6d\x65\
-\x20\x6d\x75\x73\x74\x20\x62\x65\x20\x69\x6e\x20\x74\x68\x65\x20\
-\x75\x73\x65\x72\x6e\x61\x6d\x65\x40\x70\x72\x6f\x76\x69\x64\x65\
-\x72\x20\x66\x6f\x72\x6d\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\
-\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\x00\x49\x00\x6e\x00\
-\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x50\x00\
-\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x50\x72\x6f\x76\x69\x64\x65\
-\x72\x20\x49\x6e\x66\x6f\x07\x00\x00\x00\x10\x50\x72\x6f\x76\x69\
-\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\
-\x42\x00\x45\x00\x73\x00\x74\x00\x6f\x00\x20\x00\x65\x00\x73\x00\
-\x20\x00\x6c\x00\x6f\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\
-\x64\x00\x69\x00\x63\x00\x65\x00\x20\x00\x65\x00\x6c\x00\x20\x00\
-\x70\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\
-\x72\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x54\x68\x69\
-\x73\x20\x69\x73\x20\x77\x68\x61\x74\x20\x70\x72\x6f\x76\x69\x64\
-\x65\x72\x20\x73\x61\x79\x73\x2e\x07\x00\x00\x00\x10\x50\x72\x6f\
-\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\x03\x00\
-\x00\x00\x46\x00\x43\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\x00\
-\x62\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x65\x00\x6c\x00\
-\x20\x00\x66\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x72\x00\x70\x00\
-\x72\x00\x69\x00\x6e\x00\x74\x00\x20\x00\x64\x00\x65\x00\x20\x00\
-\x6c\x00\x61\x00\x20\x00\x43\x00\x41\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x17\x43\x68\x65\x63\x6b\x69\x6e\x67\x20\x43\x41\x20\x66\
-\x69\x6e\x67\x65\x72\x70\x72\x69\x6e\x74\x07\x00\x00\x00\x1b\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\
-\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x22\
-\x00\x41\x00\x75\x00\x74\x00\x6f\x00\x63\x00\x6f\x00\x6e\x00\x66\
-\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x6e\x00\x64\x00\x6f\
-\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x11\x44\x6f\x69\x6e\
-\x67\x20\x61\x75\x74\x6f\x63\x6f\x6e\x66\x69\x67\x2e\x07\x00\x00\
-\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\
-\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\
-\x00\x00\x3e\x00\x4f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\
-\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x65\x00\x72\x00\
-\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\
-\x20\x00\x64\x00\x65\x00\x20\x00\x6c\x00\x61\x00\x20\x00\x43\x00\
-\x41\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x46\x65\x74\x63\x68\
-\x69\x6e\x67\x20\x43\x41\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\
-\x74\x65\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\
-\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\
-\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\x00\x66\
-\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x63\x00\x69\x00\x6f\
-\x00\x6e\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x50\x00\x72\
-\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x0e\x50\x72\x6f\x76\x69\x64\x65\x72\
-\x20\x73\x65\x74\x75\x70\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\
-\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\
-\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3e\x00\x56\x00\x61\
-\x00\x6c\x00\x69\x00\x64\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\
-\x00\x63\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x63\
-\x00\x61\x00\x64\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x20\x00\x6c\
-\x00\x61\x00\x20\x00\x61\x00\x70\x00\x69\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x1a\x56\x61\x6c\x69\x64\x61\x74\x69\x6e\x67\x20\x61\
-\x70\x69\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x07\x00\
-\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\
-\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x46\x00\x4e\x00\x6f\x00\x20\x00\x73\x00\x65\x00\x20\
-\x00\x70\x00\x75\x00\x64\x00\x6f\x00\x20\x00\x72\x00\x65\x00\x67\
-\x00\x69\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\x00\x28\
-\x00\x62\x00\x61\x00\x64\x00\x20\x00\x72\x00\x65\x00\x73\x00\x70\
-\x00\x6f\x00\x6e\x00\x73\x00\x65\x00\x29\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x72\x65\
-\x67\x69\x73\x74\x65\x72\x20\x28\x62\x61\x64\x20\x72\x65\x73\x70\
-\x6f\x6e\x73\x65\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x54\
-\x00\x45\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x64\x00\x6f\
-\x00\x73\x00\x65\x00\x20\x00\x61\x00\x6c\x00\x20\x00\x70\x00\x72\
-\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\
-\x00\x28\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x72\x00\x72\
-\x00\x29\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x27\x45\x72\
-\x72\x6f\x72\x20\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\
-\x6f\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\x63\x6f\x6e\x6e\
-\x65\x72\x72\x29\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x52\
-\x00\x45\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x64\x00\x6f\
-\x00\x73\x00\x65\x00\x20\x00\x61\x00\x6c\x00\x20\x00\x70\x00\x72\
-\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\
-\x00\x28\x00\x74\x00\x69\x00\x6d\x00\x65\x00\x6f\x00\x75\x00\x74\
-\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\x00\x26\x45\x72\x72\x6f\
-\x72\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\x6f\x20\
-\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\x74\x69\x6d\x65\x6f\x75\
-\x74\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\
-\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\x00\x45\x00\
-\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x64\x00\x75\x00\x72\x00\
-\x61\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x65\x00\x6c\x00\x20\x00\
-\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\x00\x6f\x00\
-\x20\x00\x28\x00\x25\x00\x73\x00\x29\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x1e\x45\x72\x72\x6f\x72\x20\x64\x75\x72\x69\x6e\x67\x20\
-\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x20\x28\x25\x73\
-\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\
-\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x40\x00\x4c\x00\x61\
-\x00\x73\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x72\x00\x61\
-\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x73\x00\x20\x00\x6e\x00\x6f\
-\x00\x20\x00\x73\x00\x6f\x00\x6e\x00\x20\x00\x69\x00\x67\x00\x75\
-\x00\x61\x00\x6c\x00\x65\x00\x73\x00\x2e\x00\x2e\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x19\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x64\
-\x6f\x65\x73\x20\x6e\x6f\x74\x20\x6d\x61\x74\x63\x68\x2e\x2e\x07\
-\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\
-\x00\x74\x00\x72\x00\x61\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x20\
-\x00\x64\x00\x65\x00\x6d\x00\x61\x00\x73\x00\x69\x00\x61\x00\x64\
-\x00\x6f\x00\x20\x00\x6f\x00\x62\x00\x76\x00\x69\x00\x61\x00\x2e\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x50\x61\x73\x73\x77\x6f\
-\x72\x64\x20\x74\x6f\x6f\x20\x6f\x62\x76\x69\x6f\x75\x73\x2e\x07\
-\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\
-\x00\x74\x00\x72\x00\x61\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x20\
-\x00\x64\x00\x65\x00\x6d\x00\x61\x00\x73\x00\x69\x00\x61\x00\x64\
-\x00\x6f\x00\x20\x00\x63\x00\x6f\x00\x72\x00\x74\x00\x61\x00\x2e\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13\x50\x61\x73\x73\x77\x6f\
-\x72\x64\x20\x74\x6f\x6f\x20\x73\x68\x6f\x72\x74\x2e\x07\x00\x00\
-\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\
-\x67\x65\x01\x03\x00\x00\x00\x5e\x00\x52\x00\x65\x00\x67\x00\x69\
-\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\x00\x75\x00\x6e\
-\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\x00\x6f\x00\x20\x00\x75\
-\x00\x73\x00\x75\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x63\
-\x00\x6f\x00\x6e\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\
-\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\
-\x00\x25\x00\x73\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x25\
-\x52\x65\x67\x69\x73\x74\x65\x72\x20\x61\x20\x6e\x65\x77\x20\x75\
-\x73\x65\x72\x20\x77\x69\x74\x68\x20\x70\x72\x6f\x76\x69\x64\x65\
-\x72\x20\x25\x73\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x30\
-\x00\x43\x00\x75\x00\x65\x00\x6e\x00\x74\x00\x61\x00\x20\x00\x63\
-\x00\x72\x00\x65\x00\x61\x00\x64\x00\x61\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x20\x00\x65\x00\x78\x00\x69\x00\x74\x00\x6f\x00\x21\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x52\x65\x67\x69\x73\x74\
-\x72\x61\x74\x69\x6f\x6e\x20\x73\x75\x63\x63\x65\x65\x64\x65\x64\
-\x21\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\
-\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x18\x00\x4e\x00\x75\
-\x00\x65\x00\x76\x00\x61\x00\x20\x00\x43\x00\x75\x00\x65\x00\x6e\
-\x00\x74\x00\x61\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x53\x69\
-\x67\x6e\x20\x55\x70\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2c\
-\x00\x55\x00\x73\x00\x75\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\
-\x00\x6e\x00\x6f\x00\x20\x00\x64\x00\x69\x00\x73\x00\x70\x00\x6f\
-\x00\x6e\x00\x69\x00\x62\x00\x6c\x00\x65\x00\x2e\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x17\x55\x73\x65\x72\x6e\x61\x6d\x65\x20\x6e\
-\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x2e\x07\x00\x00\
-\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\
-\x67\x65\x01\x03\x00\x00\x00\x34\x00\x45\x00\x72\x00\x72\x00\x6f\
-\x00\x72\x00\x20\x00\x64\x00\x65\x00\x20\x00\x61\x00\x75\x00\x74\
-\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x61\x00\x63\x00\x69\
-\x00\x6f\x00\x6e\x00\x3a\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\
-\x74\x69\x6f\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\
-\x00\x00\x1a\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\
-\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\
-\x00\x00\x34\x00\x4f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\
-\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x65\x00\x72\x00\
-\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\
-\x20\x00\x65\x00\x69\x00\x70\x08\x00\x00\x00\x00\x06\x00\x00\x00\
-\x18\x46\x65\x74\x63\x68\x69\x6e\x67\x20\x65\x69\x70\x20\x63\x65\
-\x72\x74\x69\x66\x69\x63\x61\x74\x65\x07\x00\x00\x00\x1a\x52\x65\
-\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\
-\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x52\x00\x4f\
-\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\x65\x00\x6e\x00\x64\
-\x00\x6f\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\
-\x00\x75\x00\x72\x00\x61\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x20\
-\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\
-\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x2e\x00\x2e\x00\x2e\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x46\x65\x74\x63\x68\x69\
-\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x63\x6f\x6e\x66\
-\x69\x67\x2e\x2e\x2e\x07\x00\x00\x00\x1a\x52\x65\x67\x69\x73\x74\
-\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x56\x00\x61\x00\x6c\
-\x00\x69\x00\x64\x00\x61\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x20\
-\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x63\x00\x65\x00\x72\x00\x74\
-\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x16\x43\x65\x72\x74\x69\x66\x69\x63\
-\x61\x74\x65\x20\x76\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x07\x00\
-\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\
-\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x6c\x00\x6e\x00\x6f\x00\
-\x20\x00\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\x64\x00\x6f\x00\
-\x20\x00\x6f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x65\x00\x72\x00\
-\x20\x00\x69\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\
-\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\
-\x64\x00\x6f\x00\x72\x00\x20\x00\x28\x00\x72\x00\x65\x00\x66\x00\
-\x75\x00\x73\x00\x65\x00\x64\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\
-\x6e\x00\x2e\x00\x29\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\
-\x31\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x64\x6f\x77\x6e\x6c\
-\x6f\x61\x64\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x69\x6e\x66\
-\x6f\x20\x28\x72\x65\x66\x75\x73\x65\x64\x20\x63\x6f\x6e\x6e\x2e\
-\x29\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\
-\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4a\x00\
-\x6e\x00\x6f\x00\x20\x00\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\
-\x64\x00\x6f\x00\x20\x00\x6f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\
-\x65\x00\x72\x00\x20\x00\x69\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\
-\x64\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\x00\
-\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x67\x65\x74\
-\x20\x69\x6e\x66\x6f\x20\x66\x72\x6f\x6d\x20\x70\x72\x6f\x76\x69\
-\x64\x65\x72\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\
-\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\
-\x24\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x61\x00\x20\x00\x74\x00\
-\x75\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\
-\x64\x00\x6f\x00\x72\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\x45\
-\x6e\x74\x65\x72\x20\x50\x72\x6f\x76\x69\x64\x65\x72\x07\x00\x00\
-\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\
-\x50\x61\x67\x65\x01\x03\x00\x00\x00\x9c\x00\x50\x00\x6f\x00\x72\
-\x00\x20\x00\x66\x00\x61\x00\x76\x00\x6f\x00\x72\x00\x2c\x00\x20\
-\x00\x72\x00\x65\x00\x6c\x00\x6c\x00\x65\x00\x6e\x00\x61\x00\x20\
-\x00\x65\x00\x6c\x00\x20\x00\x64\x00\x6f\x00\x6d\x00\x69\x00\x6e\
-\x00\x69\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x70\
-\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\
-\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\x71\x00\x75\x00\x69\
-\x00\x65\x00\x72\x00\x61\x00\x73\x00\x20\x00\x75\x00\x73\x00\x61\
-\x00\x72\x00\x20\x00\x70\x00\x61\x00\x72\x00\x61\x00\x20\x00\x74\
-\x00\x75\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x65\x00\x78\x00\x69\
-\x00\x6f\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x4c\
-\x50\x6c\x65\x61\x73\x65\x20\x65\x6e\x74\x65\x72\x20\x74\x68\x65\
-\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6f\x66\x20\x74\x68\x65\x20\x70\
-\x72\x6f\x76\x69\x64\x65\x72\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\
-\x20\x74\x6f\x20\x75\x73\x65\x20\x66\x6f\x72\x20\x79\x6f\x75\x72\
-\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x2e\x07\x00\x00\x00\
-\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\
-\x61\x67\x65\x01\x03\x00\x00\x00\x62\x00\x4e\x00\x6f\x00\x20\x00\
-\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\x64\x00\x6f\x00\x20\x00\
-\x76\x00\x65\x00\x72\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\
-\x72\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x63\x00\x65\x00\x72\x00\
-\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\
-\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x73\x00\x65\x00\x72\x00\
-\x76\x00\x69\x00\x64\x00\x6f\x00\x72\x00\x2e\x08\x00\x00\x00\x00\
-\x06\x00\x00\x00\x29\x53\x65\x72\x76\x65\x72\x20\x63\x65\x72\x74\
-\x69\x66\x69\x63\x61\x74\x65\x20\x63\x6f\x75\x6c\x64\x20\x6e\x6f\
-\x74\x20\x62\x65\x20\x76\x65\x72\x69\x66\x69\x65\x64\x2e\x07\x00\
-\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\
-\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x16\x00\x63\x00\x6f\x00\
-\x6d\x00\x70\x00\x72\x00\x6f\x00\x26\x00\x62\x00\x61\x00\x72\x00\
-\x21\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x63\x68\x65\x63\x26\
-\x6b\x21\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\
-\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3a\x00\
-\x63\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\x00\x62\x00\x61\x00\
-\x6e\x00\x64\x00\x6f\x00\x20\x00\x6e\x00\x6f\x00\x6d\x00\x62\x00\
-\x72\x00\x65\x00\x20\x00\x64\x00\x65\x00\x20\x00\x64\x00\x6f\x00\
-\x6d\x00\x69\x00\x6e\x00\x69\x00\x6f\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x14\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x64\x6f\x6d\x61\
-\x69\x6e\x20\x6e\x61\x6d\x65\x07\x00\x00\x00\x12\x53\x65\x6c\x65\
-\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\
-\x00\x00\x00\x34\x00\x63\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\
-\x00\x62\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x6f\
-\x00\x6e\x00\x65\x00\x78\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x68\
-\x00\x74\x00\x74\x00\x70\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\
-\x00\x19\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x68\x74\x74\x70\x73\
-\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x07\x00\x00\x00\x12\
-\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\
-\x67\x65\x01\x03\x00\x00\x00\x3a\x00\x6f\x00\x62\x00\x74\x00\x65\
-\x00\x6e\x00\x69\x00\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x69\
-\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\
-\x00\x70\x00\x72\x00\x65\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\
-\x00\x72\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x66\x65\x74\x63\
-\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x69\x6e\
-\x66\x6f\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\
-\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x88\x00\x00\x00\x02\x01\
-\x01\
-"
-
-qt_resource_name = "\
-\x00\x0c\
-\x0d\xfc\x11\x13\
-\x00\x74\
-\x00\x72\x00\x61\x00\x6e\x00\x73\x00\x6c\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\
-\x00\x05\
-\x00\x6a\x85\x7d\
-\x00\x64\
-\x00\x65\x00\x2e\x00\x71\x00\x6d\
-\x00\x05\
-\x00\x6c\x65\x7d\
-\x00\x65\
-\x00\x73\x00\x2e\x00\x71\x00\x6d\
-"
-
-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\x02\x00\x00\x00\x02\
-\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x00\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x98\
-"
-
-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/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py
index 9d16a35e..7330b67a 100644
--- a/src/leap/gui/mainwindow_rc.py
+++ b/src/leap/gui/mainwindow_rc.py
@@ -2,7 +2,7 @@
# Resource object code
#
-# Created: Wed Jan 30 06:06:54 2013
+# Created: Thu Aug 9 23:06:52 2012
# by: The Resource Compiler for PyQt (Qt v4.8.2)
#
# WARNING! All changes made in this file will be lost!
@@ -10,1063 +10,740 @@
from PyQt4 import QtCore
qt_resource_data = "\
-\x00\x00\x05\x95\
+\x00\x00\x0d\xf3\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\
-\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\
-\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\
-\x19\x80\x46\x5d\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\x1f\x74\x45\x58\
-\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\
-\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\
-\x8e\xa4\x29\xab\x00\x00\x02\x13\x50\x4c\x54\x45\xff\xff\xff\xff\
-\x00\x00\xff\x00\x00\xaa\x00\x00\xbf\x00\x00\xbf\xbf\xbf\xd5\x00\
-\x00\xc6\x00\x00\xc4\x00\x00\xbb\xbb\xbb\xcc\x00\x00\xcf\x00\x00\
-\xcc\x00\x00\xce\x00\x00\xb9\xb9\xb9\xc2\xc2\xc2\xce\x00\x00\xca\
-\x00\x00\xcc\x00\x00\xcd\x00\x00\xcc\x00\x00\xc1\xc1\xc1\xce\x00\
-\x00\xca\x00\x00\xcb\x00\x00\xcd\x00\x00\xcb\x00\x00\xcd\x00\x00\
-\xce\x00\x00\xbe\xbe\xbe\xcc\x00\x00\xbf\xbf\xbf\xbe\xbe\xbe\xcd\
-\x00\x00\xcb\x00\x00\xcc\x00\x00\xcd\x00\x00\xcc\x00\x00\xcc\x00\
-\x00\xbe\xbe\xbe\xbf\xbf\xbf\xcb\x00\x00\xcb\x00\x00\xcb\x00\x00\
-\xcc\x00\x00\xcc\x00\x00\xbf\xbf\xbf\xcd\x00\x00\xcb\x00\x00\xcc\
-\x00\x00\xcc\x00\x00\xbf\xbf\xbf\xcc\x00\x00\xcc\x00\x00\xcc\x00\
-\x00\xbe\xbe\xbe\xcc\x00\x00\xbe\xbe\xbe\xcc\x00\x00\xcc\x00\x00\
-\xcc\x00\x00\xcc\x00\x00\xbe\xbe\xbe\xbe\xbe\xbe\xcc\x00\x00\xcc\
-\x00\x00\xcc\x00\x00\xcc\x00\x00\xbe\xb7\xb7\xbe\xb8\xb8\xbe\xba\
-\xba\xbe\xbc\xbc\xbe\xbd\xbd\xbe\xbe\xbe\xbf\xaa\xaa\xbf\xab\xab\
-\xbf\xac\xac\xbf\xad\xad\xbf\xae\xae\xbf\xb0\xb0\xbf\xb1\xb1\xbf\
-\xb4\xb4\xbf\xb6\xb6\xbf\xb7\xb7\xc0\x9c\x9c\xc0\x9d\x9d\xc0\xa1\
-\xa1\xc0\xa2\xa2\xc0\xa4\xa4\xc0\xa5\xa5\xc0\xa6\xa6\xc0\xa7\xa7\
-\xc0\xa8\xa8\xc1\x8d\x8d\xc1\x91\x91\xc1\x94\x94\xc1\x95\x95\xc1\
-\x96\x96\xc1\x99\x99\xc1\x9c\x9c\xc2\x82\x82\xc2\x87\x87\xc2\x88\
-\x88\xc2\x8d\x8d\xc2\x8e\x8e\xc3\x73\x73\xc3\x74\x74\xc3\x76\x76\
-\xc3\x79\x79\xc3\x7c\x7c\xc3\x7d\x7d\xc3\x7f\x7f\xc4\x67\x67\xc4\
-\x6c\x6c\xc4\x6d\x6d\xc4\x6e\x6e\xc4\x70\x70\xc5\x59\x59\xc5\x5d\
-\x5d\xc5\x5f\x5f\xc5\x62\x62\xc5\x63\x63\xc6\x4c\x4c\xc6\x4f\x4f\
-\xc6\x50\x50\xc6\x53\x53\xc6\x56\x56\xc6\x58\x58\xc7\x3e\x3e\xc7\
-\x41\x41\xc7\x43\x43\xc7\x45\x45\xc7\x46\x46\xc7\x47\x47\xc7\x4b\
-\x4b\xc8\x31\x31\xc8\x35\x35\xc8\x36\x36\xc8\x38\x38\xc8\x3a\x3a\
-\xc8\x3c\x3c\xc9\x22\x22\xc9\x25\x25\xc9\x26\x26\xc9\x27\x27\xc9\
-\x28\x28\xc9\x2a\x2a\xc9\x2d\x2d\xc9\x2e\x2e\xca\x16\x16\xca\x17\
-\x17\xca\x1a\x1a\xca\x1b\x1b\xca\x1c\x1c\xca\x1d\x1d\xca\x1e\x1e\
-\xca\x20\x20\xca\x21\x21\xcb\x07\x07\xcb\x09\x09\xcb\x0a\x0a\xcb\
-\x0c\x0c\xcb\x0d\x0d\xcb\x0e\x0e\xcb\x0f\x0f\xcb\x10\x10\xcb\x11\
-\x11\xcb\x12\x12\xcb\x13\x13\xcc\x00\x00\xcc\x01\x01\xcc\x02\x02\
-\xcc\x03\x03\xcc\x04\x04\xcc\x05\x05\xcc\x06\x06\xcc\x07\x07\xd4\
-\x0d\x79\xbb\x00\x00\x00\x44\x74\x52\x4e\x53\x00\x01\x02\x03\x04\
-\x04\x06\x09\x0d\x0f\x0f\x10\x14\x15\x16\x19\x1a\x1d\x1e\x24\x28\
-\x29\x2a\x30\x36\x3d\x40\x42\x43\x4b\x55\x58\x5e\x60\x63\x64\x65\
-\x6e\x73\x7d\x7f\x8a\x94\x99\x9a\xaa\xb2\xbb\xbc\xc3\xc9\xca\xd2\
-\xd5\xde\xe0\xe3\xe6\xe8\xed\xef\xf6\xf7\xfa\xfa\xfb\xfc\xfd\xef\
-\xfa\x14\xec\x00\x00\x02\x79\x49\x44\x41\x54\x58\xc3\xed\x96\xd7\
-\x5b\x13\x41\x14\x47\x47\x05\x15\x5b\x2c\xa0\x58\x62\xb0\x26\xb6\
-\xa0\x46\xc5\x28\x12\x6c\x49\xae\x0d\x62\x01\xc5\x82\x58\x10\x7b\
-\x2f\xd8\x1b\x8a\xbd\x00\x62\x41\x45\x14\xf5\x04\x51\xff\x44\x1f\
-\x76\x37\x1f\x09\xc9\x66\x37\x8f\xc8\xef\xed\xee\xf7\x9d\xb3\x33\
-\x73\x67\x66\x57\xa9\xfe\x98\x65\x94\xd3\xe3\xf5\xfb\xbd\x1e\xe7\
-\xa8\x8c\xf0\x6c\x77\x40\xf4\x04\xdc\xd9\xf6\xf9\x3c\x9f\xf4\x88\
-\x2f\xcf\x2e\x5f\x50\x22\x71\x29\x29\xb0\xf9\xfe\x04\x5e\xa4\xc4\
-\xd6\x18\xb2\x7d\xd2\x2b\x3e\x3b\xeb\xe0\x96\x24\x71\xdb\xe8\x5f\
-\x20\x99\x20\x60\xbd\x9b\x4e\x83\x29\x72\x39\x1c\xae\x22\xa3\x72\
-\x5a\x16\x78\x0c\x3e\x47\x29\xa5\x72\x0c\x83\xc7\xb2\xc0\xab\x13\
-\x2e\xad\x74\xe9\xa5\xd7\xb2\xc0\xaf\x13\x0e\xad\x74\xe8\xa5\xdf\
-\x12\x3c\x31\x57\x29\x63\xd2\xc6\xb3\x58\x3d\x60\xda\xa0\x74\x7c\
-\x6e\x71\xf1\x94\x94\x82\x21\x0b\x98\x9b\x86\x1f\xb9\x0c\x56\xcf\
-\x4c\x21\xd8\xb4\x18\x98\x65\x2e\x28\x04\xe0\x66\x38\x99\xe0\x60\
-\x0b\xc0\x9a\x7c\x53\xc1\x84\xe5\x00\x3c\xd9\xda\x7b\x1f\x1d\x6e\
-\x07\x60\xce\x40\xf3\x21\x8c\x7e\x01\xc0\xf3\x0d\x89\xfc\xd9\x2e\
-\x80\xee\xa3\x69\xbb\xb0\xed\x29\x00\x3f\x0f\xc4\xe1\xa1\x6b\x00\
-\x7c\x39\x22\x69\x05\x12\xbe\x05\xc0\xa7\x43\x3d\xf8\x48\x23\x00\
-\xad\x15\x62\x41\x20\x72\xac\x1b\x20\x7a\x26\xc6\x57\xb5\x01\xd0\
-\x54\x26\xd6\x04\x52\xd7\x01\xc0\x83\xa0\xc6\x57\x77\x02\x70\x2f\
-\x28\x56\x05\x52\xf9\x16\x80\x87\xe5\x22\x22\x27\x7f\x03\x44\xaf\
-\xc4\xb7\xd6\x5c\x20\x65\xcd\x00\xbc\xdf\x29\x72\x19\x80\x3f\xa7\
-\xc4\x96\x40\x82\x0d\x00\x7c\xdd\x7b\x17\x80\xce\x6a\xb1\x29\x10\
-\xa9\x8f\x02\x44\x01\x68\xab\x12\xfb\x02\x39\xfd\x17\x3d\x8d\x11\
-\xc9\x44\x20\x35\x5a\x33\xb8\x1a\x92\xcc\x04\x17\x7e\x69\x82\x57\
-\x9b\x33\x12\x84\x6e\x18\x33\xa0\x65\x7b\x06\x82\xc8\x63\x00\x3e\
-\x03\xd0\x5e\x6b\x5b\xb0\xe7\x19\x00\x2f\x37\x9e\xef\x02\xe8\x3a\
-\x67\x53\x50\xf3\x03\x80\xdb\xeb\x44\x6a\xb5\x7b\xe0\x7a\xc8\x8e\
-\x40\xef\xe0\xc5\xb0\x88\xc8\x8e\x37\x00\x3c\x8a\x58\x17\x68\x7b\
-\xa8\xfb\xb8\x3e\xea\x2d\xaf\x01\xf8\xb8\xdb\xa2\x20\x78\x1f\x80\
-\x8e\xba\xd8\xc2\xad\xbf\x03\xc0\xf7\xfd\x96\x04\x65\x4d\x00\xbc\
-\xab\xec\xd1\xfc\xf0\xa5\xd8\x89\x4a\x2b\xa8\x68\x05\xa0\xb9\x3c\
-\xfe\x4a\x3c\x61\x9c\xe9\x74\xfc\x64\x6d\xfb\x36\x04\x13\x2f\xd5\
-\x7d\xdf\xb4\x5b\x65\xb0\x39\x3f\xa3\x14\x20\x5a\x9f\xe4\xf7\x60\
-\xd7\x07\x00\x16\x8d\x30\x15\xcc\x03\x58\x35\x35\xf1\x9b\xa8\x94\
-\x52\x6a\xf8\x42\x80\x95\xe3\x4c\x05\x59\x85\xb0\x22\x5f\x25\x15\
-\xa8\xac\xf9\x50\x3a\x29\xcd\x1a\x0c\x5b\xba\x64\xac\x4a\x21\x50\
-\x6a\xf6\xda\xe9\x69\xbb\x30\x66\xa8\x4a\x2d\x50\xe3\xed\xfd\x2d\
-\x5a\x3e\x40\x7d\x44\x20\x36\xd3\x2f\xe8\x9b\x82\xff\x38\xff\x00\
-\xc1\x36\x30\x95\xf0\x66\xca\x60\x00\x00\x00\x00\x49\x45\x4e\x44\
-\xae\x42\x60\x82\
-\x00\x00\x04\xec\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
-\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\x06\xec\x00\x00\x06\xec\
-\x01\x1e\x75\x38\x35\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\x13\x74\x45\
-\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\
-\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x4a\x49\x44\x41\
-\x54\x48\x89\x8d\x96\x5d\x6c\x53\x65\x18\xc7\x7f\xef\x39\x6b\xbb\
-\x7e\x9c\x75\x65\xad\x2b\x9b\xfb\xd0\x31\xdd\x14\xb6\x8c\x19\x44\
-\x90\x44\x63\x82\x42\x88\x5e\x90\x98\xcc\x19\x15\x13\xd4\x18\x76\
-\x61\xd4\x18\xe3\x85\x57\xca\x05\xe1\xc2\x0c\xa3\xa8\x51\xd0\x4c\
-\x12\xe3\x85\x31\x80\x26\x6a\xe2\x85\x23\xb0\x38\xb6\xc1\x1c\xce\
-\xb1\x40\x59\xf6\xe5\xca\xda\xae\xed\xfa\x75\x7a\x5e\x2f\x4e\xd7\
-\x59\xd6\x32\xfe\xc9\x7b\xf3\x9e\xe7\xf9\xff\x9f\xe7\xff\x9e\xf3\
-\x9c\x57\x48\x29\x59\x0f\xbd\x7b\x85\x0d\x17\xed\x1e\xbb\xb2\x07\
-\x20\x94\x30\x7e\x22\xc6\x48\xcf\x59\x99\x5a\x2f\x57\x94\x12\xf8\
-\xec\x55\x61\x71\x65\x6d\x47\xfc\xbe\xda\x47\x9d\x5a\xa5\xbf\xda\
-\x69\xaf\xda\xe0\x28\x2f\x07\x58\x5c\x4e\x26\xe7\xe3\x89\x9b\xf1\
-\x68\x78\x6e\x6e\x61\xfa\x8f\x98\x9a\x7a\xfb\x95\xe3\x32\x73\xc7\
-\x02\x9f\x76\x89\x8e\xba\xda\xda\x2f\xb7\x37\xdf\xdf\xe6\x2a\x13\
-\x8a\x94\x06\x82\xc2\x38\x89\x40\x08\x85\x98\x2e\x8d\xf3\x13\xe3\
-\x97\xa6\xa6\xa7\x5f\x7e\xed\x94\x1c\x5a\x57\xa0\xef\xa0\xfd\x70\
-\x5b\xf3\x96\x03\xcd\xde\x8a\x6a\x61\x64\xd7\x73\xc0\x14\x53\x54\
-\x26\x82\x4b\xf3\x97\x26\x2e\x7f\xd5\xfd\x79\xe2\xdd\x92\x02\x27\
-\x5f\x2a\x7b\xe1\x89\xce\x1d\xc7\xbc\x76\x55\x13\xc5\x98\xac\x4e\
-\x10\x0a\xa4\xa2\x6b\x45\x80\x60\x22\x1b\xfd\x6d\xf0\xdc\xa1\x17\
-\x4f\xe8\x5f\xaf\x11\x38\xfa\x9c\xf0\x6e\xdb\xf4\xc0\xf9\x6d\xf5\
-\xfe\x26\x30\xf2\x89\xca\xc6\x76\xd4\x07\xf7\xa3\xd4\x74\x80\xd5\
-\x65\x6e\xa6\xe3\x64\x03\xfd\x64\x2f\x9e\x40\x46\x67\xff\x27\xa3\
-\x30\x70\x63\x6e\x72\xe0\xea\xd8\xf6\x37\xbf\x95\x41\x73\x27\x87\
-\x06\x8f\xa7\x6f\x6b\x7d\x4d\x01\x39\x80\x52\xff\x08\x4a\xe3\xae\
-\x55\xf2\x5c\x27\x6a\xf3\x6e\x2c\x7b\x8f\x9a\x5d\xe5\x61\xb0\xb5\
-\xbe\xa6\xa9\xc1\xe3\xe9\x5b\x95\x04\x7a\xbb\x44\x47\x5b\x53\xcb\
-\x4e\x15\xbd\x98\x31\xc8\x70\x00\xfd\xfc\xc7\x64\xce\xbc\x81\x7e\
-\xe1\x13\xc8\x75\x2d\xb4\x8d\x28\xb5\x0f\x15\xc4\xaa\xe8\xb4\x35\
-\xb5\xec\xec\xed\x12\x1d\x00\x65\x00\xee\x72\x65\x9f\x5f\x73\x38\
-\x05\x6b\x0f\x35\x3b\xf6\x03\xfa\xc0\xf1\x3c\x29\xb3\xc3\xa8\xf7\
-\x3e\x8e\xf0\xb5\x98\x22\xf6\x0d\x05\xf1\x02\xf0\x6b\x0e\xa7\xbb\
-\x5c\xd9\x07\x0c\x29\x00\x9a\xc3\xd5\x69\x55\xd5\xe2\xd5\x47\xe7\
-\x56\xc9\x01\xe1\xbe\x1b\xe1\xb9\x67\xf5\x79\x70\x7c\x4d\x8e\x55\
-\x55\xd1\x1c\xae\xce\xbc\x45\x15\x6e\x5f\x9d\x90\xc5\xed\x29\xa8\
-\xae\xa2\x06\xcb\x53\x47\xa0\xcc\x66\x76\x37\xfa\x3d\xd9\xa9\x81\
-\xb5\x71\x52\xa7\xc2\xed\xab\x83\x9c\x45\x76\xbb\x56\x25\xa5\xa4\
-\xe8\xab\xb9\x02\x9b\x86\x65\xf7\x87\x08\xcd\x6f\x92\x8f\x9f\x21\
-\xf5\xdd\xf3\xa0\xa7\x10\xe5\x6e\x44\x45\x2d\x38\x7d\x08\x21\x90\
-\xd2\xe4\xcc\x0b\x24\x12\xd1\x9b\x42\xbd\xab\x81\x6c\xba\x28\xb7\
-\x94\x06\x65\xcd\x4f\x22\x2a\x1b\x00\x30\xa6\xff\x24\xd5\xb7\x1f\
-\x74\x73\x14\xc9\x64\x04\x99\x8c\x80\xc5\x8e\xe2\xae\x03\xab\x93\
-\x44\x22\x7a\x33\x6f\xd1\x52\x64\x61\x0a\xb5\xbc\x28\xb1\xb1\x34\
-\x83\x91\xb3\xc1\x98\x1d\xc1\x98\x1d\x41\x3f\xd7\x9b\x27\x2f\x40\
-\x26\x81\x11\xfc\x07\x99\x8a\x99\x9c\x2b\x1d\x44\x97\x63\x83\xc9\
-\xe8\xfc\x33\x36\x23\x05\xaa\x05\xd2\xcb\xc8\x74\xcc\xfc\x88\x72\
-\x5d\xa5\x7f\x3c\x74\x3b\x03\x0b\x90\x52\xed\x44\x97\x63\x83\x79\
-\x81\x48\xd2\x38\x3d\x1b\xcf\xbc\x53\x1f\xb9\xe4\x44\x1a\x45\x93\
-\xac\xcf\x7e\x83\xda\xb8\xcb\x2c\xf4\xd7\xf7\xd1\x2f\x9e\x2c\xce\
-\x2e\x14\xe6\xd2\x65\xf1\x48\xd2\x38\x0d\x39\x8b\x7a\x4e\xc9\xa1\
-\xd1\xc0\xb5\xfe\xac\xb7\xb5\x64\x55\xc2\xe5\x47\x54\x36\x98\xe7\
-\x60\xd3\x4a\xc6\x65\xbd\xad\x8c\x06\xae\xf5\xf7\xe4\x26\x6b\x7e\
-\x54\x04\x42\xa1\xee\xe1\x90\x31\x29\x1c\xde\xd2\xbd\xaf\x03\xe1\
-\xf0\x32\x1c\x32\x26\x03\xa1\x50\x77\x7e\xef\xd6\x69\xfa\x58\x7b\
-\xe7\x31\x5f\x78\x54\x23\xb3\x5c\x90\xac\xf8\xdb\x10\x0e\xf3\xab\
-\x35\x82\x13\xc8\xa5\xe9\x42\x76\x8b\x83\x85\xca\xcd\xd1\xdf\x47\
-\x06\x8b\x4f\xd3\x15\xf4\x1d\xb4\x1f\xde\xd2\xd4\x7a\x60\x93\x1a\
-\xaa\x26\x74\xfd\xce\x4a\xf7\x34\x72\x35\xeb\x99\xbf\x3c\x79\xe5\
-\xf6\xff\x83\x15\x7c\xf0\xb4\xd8\xbe\xb9\xa9\xe6\x8b\x1d\x0d\xd5\
-\xad\xae\xd8\x94\x22\x13\x21\x90\xb7\xcc\x29\xa1\x22\xec\x1e\x62\
-\xae\x3a\xa3\xff\xfa\xfc\xdf\xe7\xc6\x66\x5e\x3f\xf2\x0b\xfd\x52\
-\x16\x8e\x84\x02\x01\x21\x84\x0a\x54\x01\x95\x9a\x1d\xdf\x7b\x7b\
-\xac\x6f\xdd\x57\xb7\xb1\x6d\x83\xbb\xd2\x53\xe3\x10\x2e\x9f\xcd\
-\xb0\x00\xfc\x9b\x54\xf4\x99\x84\x8c\x2d\x86\xc3\xe1\x2b\x81\xd9\
-\xbf\x0e\xff\x9c\xfe\x28\x9e\x22\x08\x84\x80\xb0\x94\x32\x5c\xb2\
-\x03\x21\x84\x13\xf0\x00\xee\xdc\xd2\x5c\x56\x3c\x5b\xeb\x69\x79\
-\xb8\x51\x74\x18\x12\xe5\xc2\x75\x39\x3c\x74\x83\xc9\x78\x86\x10\
-\x10\x03\x96\x80\x48\x6e\x2d\x4a\xb9\x7a\x01\x28\x79\xab\xc8\x89\
-\x59\x00\x2b\x60\xcb\x2d\x0b\xa0\x02\x3a\x90\x02\xd2\x40\x12\xc8\
-\x48\x79\xab\x87\x26\xfe\x03\x26\x93\xd5\x41\x51\x76\x98\xdb\x00\
-\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
-\x00\x00\x01\xaa\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\
-\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\
-\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\
-\x19\x80\x46\x5d\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\x1f\x74\x45\x58\
-\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\
-\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\
-\x8e\xa4\x29\xab\x00\x00\x00\x36\x50\x4c\x54\x45\xff\xff\xff\xbf\
-\xbf\xbf\xbb\xbb\xbb\xb9\xb9\xb9\xc2\xc2\xc2\xc1\xc1\xc1\xbe\xbe\
-\xbe\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbf\xbf\xbf\xbf\xbf\xbf\
-\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xf1\xb6\xe9\xa5\x00\x00\x00\x11\x74\x52\x4e\x53\x00\x04\
-\x0f\x16\x19\x29\x4b\x58\x5e\x7d\x7f\xb2\xca\xe0\xe6\xf7\xfa\x2a\
-\xb3\x5d\x53\x00\x00\x00\x9e\x49\x44\x41\x54\x58\xc3\xed\x95\xb9\
-\x12\x83\x30\x0c\x05\x31\x18\x1b\x1f\x18\xeb\xff\x7f\x36\xc9\x20\
-\xcd\x24\xe1\x92\xe8\x00\x6d\xf7\x8a\x5d\xc0\x2e\x68\x1a\x65\x8f\
-\xce\xc5\x54\x4a\x8a\xae\x3b\xa5\x9b\x50\x01\xa9\xc1\xc8\xfd\x3e\
-\xc3\x17\xb9\x97\xfa\xc3\x04\x3f\x4c\x83\xf0\xf9\x7f\xfe\xbb\x20\
-\x7a\x07\x93\x61\x41\x96\x9c\x43\x80\x15\x82\xe0\xfe\xea\x5a\xa0\
-\xf2\x6f\xd3\x91\x33\x7a\x6b\xfd\x48\xcb\xb1\x03\x91\xfc\xf6\xb3\
-\x5a\x2a\x44\x76\x20\xa1\xe1\xe7\xe9\x71\x26\x76\xa0\xa0\x61\xe7\
-\x69\x71\x16\x76\x80\x3e\x7a\x6b\xdf\x3d\x00\x07\x68\x40\x03\x1a\
-\xd0\x80\x06\x9e\x15\xd8\xfb\xc1\x88\xd1\xc0\xe5\x02\x20\x44\x03\
-\xf7\x0c\x3c\x98\x17\xb4\xcd\x62\x13\x3b\x4c\x60\xe6\x00\x00\x00\
-\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
-\x00\x00\x02\xc8\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\
-\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\
-\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\
-\x19\x80\x46\x5d\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\x1f\x74\x45\x58\
-\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\
-\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\
-\x8e\xa4\x29\xab\x00\x00\x00\xb4\x50\x4c\x54\x45\xff\xff\xff\xff\
-\xff\xff\x80\x80\x80\xbf\xbf\xbf\xcc\xcc\xcc\xbf\xbf\xbf\xc6\xc6\
-\xc6\xb3\xb3\xb3\xc8\xc8\xc8\xc3\xc3\xc3\xba\xba\xba\xc4\xc4\xc4\
-\xbd\xbd\xbd\xb9\xb9\xb9\xb9\xb9\xb9\xbf\xbf\xbf\xbc\xbc\xbc\xbd\
-\xbd\xbd\xbe\xbe\xbe\xbd\xbd\xbd\xbc\xbc\xbc\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbf\xbf\xbf\xbd\xbd\xbd\xbe\xbe\xbe\
-\xbf\xbf\xbf\xbd\xbd\xbd\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\
-\xe4\x72\x0e\xe3\x00\x00\x00\x3b\x74\x52\x4e\x53\x00\x01\x02\x04\
-\x05\x08\x09\x0a\x0e\x11\x1a\x1a\x1f\x21\x2c\x2c\x35\x36\x3b\x3e\
-\x41\x43\x47\x4b\x4e\x50\x55\x56\x57\x59\x63\x66\x71\x86\x89\x90\
-\x95\x96\x9d\x9e\xa7\xaa\xad\xb5\xb8\xbe\xc0\xc3\xc5\xc9\xcd\xd6\
-\xe0\xe6\xef\xf5\xf7\xfc\xfd\xec\xba\xa4\x27\x00\x00\x01\x14\x49\
-\x44\x41\x54\x58\xc3\xed\x95\x5b\x57\x82\x40\x14\x85\x9d\x44\xb4\
-\x40\x50\x29\x92\x64\xb2\xd4\xee\xf7\xbc\x54\xf2\xff\xff\x97\xb2\
-\x98\x91\xe2\x20\xed\xe3\x4b\x2b\xe4\x7b\xfb\xd6\x39\x7b\x73\x99\
-\x87\xa9\xd5\x2a\x8a\x68\x58\xae\x17\x04\x9e\x6b\x35\x76\x8a\x0b\
-\x27\x94\x8a\xd0\x11\xfc\x7c\xcb\x97\xdf\xf0\x5b\xdc\x7c\x7b\x20\
-\x7f\x30\x68\x33\x9f\x9f\xc9\xaf\x1b\x58\xef\x20\x7c\x49\xf0\x39\
-\xff\xc1\x91\x39\x38\x8c\xf3\x0b\xf3\x0a\x42\xfc\x34\x2d\x9d\xe9\
-\xdb\xa6\x69\xf7\xb5\x59\x70\x81\xab\xf3\x46\x6c\x86\x6e\x70\xe1\
-\x02\x4f\x25\xec\x44\x6d\xa5\x1e\x5c\x10\xa8\x84\x99\xa8\xa9\x34\
-\x80\x0b\xf4\x47\x6f\xf3\xb2\x17\xc8\x5f\xa8\x0a\xfe\x45\xc1\xc9\
-\x9a\x6d\xe1\x78\x56\xdd\x76\xc5\x74\xc7\x6f\xef\x93\x63\xdc\x33\
-\x18\x97\x5f\x51\xcc\x55\x13\x73\xc2\x28\x52\xdc\x60\x9e\xa5\xb7\
-\xd4\x0b\xd1\x29\xe2\x84\xeb\xcd\x3c\xba\x47\x9c\x30\x4d\x17\xe6\
-\x88\x13\x66\xe9\xc2\x42\x00\x4e\xb8\x4d\x17\x1e\x11\x27\x9c\xa5\
-\x0b\x43\xc4\x29\x77\x7a\xfe\x24\x20\x27\x1c\x3e\x24\xf3\x97\x23\
-\xcc\x73\x38\x7f\xfe\xf8\x7c\xbd\x38\x80\x3d\x87\xba\xc1\xf3\x3d\
-\xa0\xa3\xd8\xb9\x80\x7d\xad\xff\x6d\x81\x64\x52\x15\x94\xb3\x60\
-\x8f\x59\x01\x25\xba\xb5\x2a\xd7\xa3\x29\x75\x00\x00\x00\x00\x49\
-\x45\x4e\x44\xae\x42\x60\x82\
-\x00\x00\x27\x74\
-\x89\
-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
-\x00\x00\x80\x00\x00\x00\x66\x08\x06\x00\x00\x00\x03\x23\x99\x54\
+\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\x02\x3a\x00\x00\x02\x3a\
-\x01\xfe\x36\x29\x51\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\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\x26\xf1\x49\x44\
-\x41\x54\x78\xda\xed\x9d\x05\x58\x54\x59\xff\xc7\x7f\x77\x86\x14\
-\x90\x50\x14\x41\x94\x10\x51\x09\x45\xb0\x50\xb0\x10\x10\x13\x6b\
-\x2d\x6c\xb0\xdd\xb5\xdb\x35\xd7\x5a\x5b\x09\xdb\xb5\xdb\xb5\x0b\
-\x19\x42\xba\x15\x24\x0c\x14\x29\x15\x29\x25\x06\x66\x7e\xff\x73\
-\x86\xb9\x38\xb0\x60\xbc\xaf\xfe\x5f\x70\x67\x9e\xe7\xf3\xec\xaa\
-\xc0\x1d\xe6\xfb\xb9\xe7\x9e\x3e\x80\x88\x20\xe5\xdf\x8b\xf4\x43\
-\xf8\x9a\x0f\x09\x40\x51\x2a\xc0\xbf\x27\x6c\x5d\x42\x77\xc2\x28\
-\x42\x34\x21\x9f\xc0\x27\xcc\x21\x34\x94\xf8\x3a\x19\xa9\x00\x3f\
-\x4f\xe8\xfa\x84\x49\x84\xab\x84\x62\x02\xd6\x40\x1e\xe1\x00\x21\
-\x96\xb0\x59\x2a\x40\xdd\x0f\xbe\x1d\xe1\x2d\x1b\x70\x23\x79\x79\
-\xec\xa7\xa5\x85\xab\x5b\xb7\x46\x2f\x73\x6b\xf4\x6c\xb0\x12\x97\
-\xa8\x4e\xc6\xd1\x4a\x4e\x68\x2d\xdf\x0e\x75\xb8\x8d\x90\x03\x1c\
-\x56\x86\x9b\x52\x01\xea\x5e\xe0\xc3\x08\xbb\x08\xd3\x09\xf3\x08\
-\x6f\x54\x64\x64\x70\x93\xa9\x29\xbe\x74\x74\x44\x1c\x32\xa4\x82\
-\xe2\xc1\x83\x05\x99\xba\xde\xc2\x77\xba\xfe\x28\x49\x82\xf6\x55\
-\x6c\x29\xdb\x9c\xfe\xb0\xd4\x9f\xe1\x31\xf0\x6f\x13\x60\x11\x7b\
-\xb7\x73\x18\x06\x27\xe9\xe9\x61\x66\xbf\x7e\x95\x82\x97\x24\xdb\
-\xe4\x68\x5c\x55\x01\x28\x8f\xb5\x2f\xa3\x9e\x8c\x0e\xfd\x39\x01\
-\x04\x6d\xa9\x00\x75\x23\x7c\x2f\x1a\xbc\xa5\x66\x7d\xf4\xb0\xb0\
-\xc0\x94\x2a\x77\x7c\x75\x14\x77\x5f\xe9\x53\x9d\x00\x94\xa8\x26\
-\xe7\x45\x8f\x04\xf2\x33\x93\x08\x06\x52\x01\x6a\xbf\x00\x9a\x84\
-\xe7\x2b\x26\x70\x30\xca\xa5\xfd\x17\xc3\xa7\x94\x0d\x18\x17\x56\
-\x93\x00\x94\xb0\x26\xa7\xb1\x31\xb7\x01\xfd\xe1\x45\x84\x38\x82\
-\x8e\x54\x80\xda\x2b\x00\x43\xc8\x98\x3b\x06\x30\xf5\x06\x60\xc9\
-\x1c\x9b\xf8\x2f\x4a\xe0\x3c\x24\xff\x5d\x33\xbf\xb2\xcf\x49\xf0\
-\x50\xeb\x38\x36\xe0\xa8\xb2\x15\xc3\x35\x3f\xf8\xfd\xdb\x12\x8e\
-\x88\x1f\x3d\x43\xe9\xdf\x49\x05\xf8\xb6\x0f\xd1\x63\xe2\x40\xc0\
-\x1c\x1e\xe0\xfb\x07\x90\x57\x3a\xab\xfb\x17\x25\xc8\x6b\x75\xe6\
-\xc9\xe7\x04\xa0\xb8\x6b\xac\x60\x05\x08\xfb\x01\xef\xd9\x98\x70\
-\x81\xca\x4b\xaf\xa1\xa4\xda\x00\xad\xec\x7f\x41\x0e\x87\x4b\xff\
-\xf1\x11\x15\x41\x2a\xc0\xd7\x7f\x98\x9e\x43\x7a\x95\x0b\x20\xc2\
-\x07\xde\x97\xce\xe8\x91\xf0\x39\x01\x8a\xba\xad\xe1\x7d\x49\x80\
-\xe9\x03\xf7\xa3\x4d\xa7\xc9\xac\x04\x7f\x7e\xc7\xf7\x3b\x88\xf6\
-\x3d\x34\x92\xd7\xc2\xfa\xb6\xe3\xb1\xdb\xbe\x68\xec\xeb\x1e\x8e\
-\x93\x4e\x25\x09\xe7\x1c\x8b\xc2\x96\x96\x3d\xd8\x6b\x3e\x24\x58\
-\x48\x05\xf8\xfc\x87\x29\x4f\xc8\xe9\x69\x25\x21\x00\xe5\x01\x64\
-\x97\x4e\xef\x95\x54\x63\x3d\xa0\xdf\xc4\xe0\xcf\x85\xef\x6d\x1b\
-\xf0\x68\xd3\xf2\xe7\xb8\xfc\xd7\x60\x36\x8c\xb4\xef\x54\xdc\xaf\
-\xe6\x32\x5c\xe1\x9e\xd6\x1e\xd8\x65\xf8\x45\x64\xd6\x3d\xc1\x4e\
-\x07\x9e\xa6\x39\xdd\x42\x64\x71\xbc\xf2\x11\x3b\xad\xb9\x86\x5c\
-\x05\x25\x51\x8b\x44\x2a\xc0\xe7\x3f\x54\x2b\x1a\x90\x85\x71\x15\
-\x01\x44\x25\x01\xf3\xb6\x6c\x6a\xaf\xa7\xd5\x09\x20\x1c\x32\x34\
-\xbb\xa6\xf0\xdf\x36\x0b\x28\xfb\x73\xc9\xf3\x64\x2a\xc0\xba\xc5\
-\xf1\x58\xaf\xe5\x50\x7a\xa1\xa7\xff\xed\xb8\x03\xe1\x9c\x22\x47\
-\x11\xcf\xb7\xbb\x82\xa3\x06\xdd\x10\x02\x09\x9f\xc2\xac\x4f\x10\
-\xd8\x9c\xc9\x8e\x1b\x72\xb2\x18\x6d\x3c\x62\xb1\xdd\xa2\x13\xa8\
-\xd0\xb0\x29\xfd\xa6\x77\x84\x66\x75\x52\x00\x71\x30\x46\xdf\xa3\
-\x42\xf3\x85\xeb\xb4\xa1\x02\xe8\x6b\x57\x23\x00\x21\x97\xc7\x64\
-\x09\xa6\xf4\x7e\x5e\x9d\x04\x39\x2d\x2f\x3d\xab\x4e\x80\xdb\xfd\
-\xa3\xfd\x69\xf8\x94\x51\x2e\x97\xb1\xe9\xcc\xb7\xf4\x42\x31\xff\
-\xc5\x7b\xec\x4a\x08\x57\x97\x55\x47\x9f\x8e\x01\xb8\xbc\x9f\x3f\
-\x1f\xd6\xc6\x0b\x68\xf8\xf2\x6b\x13\x0b\x7a\xce\x7b\x19\x36\x7b\
-\xd2\x8b\x0f\x33\xc6\xc4\xb0\xa5\x0d\x5f\xdc\x2d\xdd\xb5\x4e\xd6\
-\x01\xc8\x6b\x3e\x41\x20\xd1\xcf\xbe\x91\x50\xef\x07\x5d\xab\x19\
-\xbd\x8e\x7a\xfd\xea\x05\x10\x49\xf0\x80\xc9\x14\x4c\xb6\x4b\xa9\
-\x2a\x40\x61\x97\x3f\xfc\xaa\x86\x9f\xa5\x17\x50\xb4\x79\xe9\xf3\
-\x74\x1a\xfe\x8a\x25\x2f\x22\xf5\xe6\x95\x60\xb3\x05\x02\x64\x38\
-\x72\xb1\xdf\xf8\xbe\x5a\xd3\xe2\x9e\xf0\x44\x51\x46\x19\xed\x0d\
-\xc7\xe0\x8d\x8e\xf7\xf1\xa8\x53\x5c\x21\x09\xbe\x50\x6f\xd5\xcb\
-\xc7\xe3\xa6\xa7\x3e\x5a\x31\xb3\x50\xb0\xe6\x57\xc4\xd9\xe3\x12\
-\xb0\x6d\x2b\x17\xfa\x8d\xf7\xea\x6c\x33\x90\xbc\x38\x84\xbd\xa2\
-\xe0\x3b\x8f\xc3\xb6\xee\xbe\xa8\xda\x67\x12\x82\xbc\xe8\x59\x96\
-\x46\x58\x49\x68\xfc\x9d\xaf\x59\x4f\xd4\x03\xc8\x21\x2d\x00\x9f\
-\x9a\x25\x20\x8f\x83\x74\xc1\x64\xfb\x57\x92\x02\x94\x3a\x4d\x79\
-\x58\x55\x80\x8b\x23\x1e\xf1\x68\xf8\x6b\x97\xa7\x3c\x36\x58\x50\
-\x56\xd8\x6c\x81\x10\x29\x5c\x65\x6d\x7a\x31\xcb\x2f\xbc\x17\x5a\
-\xe2\xad\xa0\xb5\x78\x86\xe1\xa0\xa5\x76\x2f\x5c\x6a\x73\x08\x6f\
-\x8c\x79\x83\xde\xe3\x3f\xe2\x61\xc7\xa0\xd7\xc3\x17\xbd\x09\x5a\
-\x31\xb7\x38\x83\x86\xbe\x62\x56\x2e\x4e\x1e\xf5\x37\xea\x36\xe9\
-\xc8\xde\xf9\x2f\x69\x93\xb0\x4e\x0a\x40\x5e\xb2\x84\xd3\x20\xab\
-\x88\x30\xee\x10\xca\xac\x7d\x54\x36\x80\x14\x9a\xfd\x22\x05\xa8\
-\xb5\xe8\x14\x32\xdd\x5d\x59\x11\x4a\x08\xe7\x09\xd3\xe8\x5d\xf2\
-\x9d\xae\x5d\x4a\x3f\xc0\xf4\x3b\x50\x52\xa3\x00\x14\x6f\x26\x4d\
-\x30\xc1\xfe\xf5\xa7\xfe\x80\xa1\xe9\x92\xe1\xa7\xb5\x78\xf8\x7e\
-\xd3\xb2\xe7\xb9\x1b\x96\xbd\x78\x66\xbc\xb0\xec\x3d\x1b\x3e\x45\
-\xae\x91\x05\xbd\xd0\xe2\x1a\x46\x1c\x97\x10\xa2\xe8\x7b\xd0\x53\
-\x6b\x8d\x6e\x96\xeb\xf1\xd0\xc0\x20\x3c\xe4\x74\x1d\x8f\xd9\x6d\
-\x29\xe2\x39\x4c\x49\xf5\x1b\xec\x70\x6f\xd1\x8c\x5d\x89\xdb\xb6\
-\x79\xe7\x78\x79\x46\xe0\x86\xf5\x37\xb1\xad\xb9\x2d\x8a\x47\x29\
-\xb7\x10\xfa\x10\xb8\x75\xb2\x23\x48\x3c\x18\x13\x00\x8d\x8d\x11\
-\x56\xc5\x22\x78\x21\xb6\xba\xfc\x21\x82\x0a\xc0\x62\x71\x2c\x86\
-\x2f\x33\xfd\x28\xc2\x5c\x62\xbb\xbd\x0c\x82\x2e\x31\x9e\x11\x59\
-\x9f\x25\x6e\x0f\x53\x21\x9a\xfe\x87\xe3\xfc\x54\x2a\x4c\xbc\x04\
-\x85\x9f\x15\x40\xd4\x3a\x60\x5e\x09\x26\x3a\xa6\xb1\x12\xbc\x37\
-\xba\xf6\x8a\x15\xe0\xf8\xc4\x78\xde\x1f\xcb\x5e\xa4\x9b\x2c\x2c\
-\x49\x93\x0c\x9f\xa2\xa0\xe7\x40\x2f\x36\x5d\x7c\x4d\x6d\xc2\x6f\
-\x84\x60\xd1\x68\xa3\x0a\xe0\xf8\xe6\x4d\x71\x6f\x47\x07\xbc\xd5\
-\xdd\x09\x53\xed\x74\x05\xe8\x20\x87\x94\x8f\x4e\xb2\x61\x27\xc6\
-\x37\xbb\xe3\xe6\xba\x9b\xef\xe6\xe6\x85\xe3\xc7\xff\x89\xfd\xfa\
-\xb9\xa2\xac\xac\x3c\xfd\x41\x7e\xb4\x0e\x53\xa7\x7b\x02\xc5\x95\
-\x30\x3e\x74\x18\x85\xb0\xbb\x40\x14\x3e\x78\x0a\x72\xfb\x45\x61\
-\xb1\xa4\x00\x94\xbe\x61\xfc\x22\xcd\x75\xa7\xb2\x80\x37\x49\x08\
-\x3b\xda\x22\x6c\xe1\x22\x4c\x21\x12\x58\x13\xd4\x2a\xc6\xe6\xe9\
-\x64\x8d\xf5\xb4\xb8\xa5\x77\x04\xc1\x84\x30\x9e\xb0\x9b\x70\x99\
-\xb0\x96\xe0\x2c\xee\x48\xd1\x20\xf8\xb3\x03\x41\xbc\xfd\x5f\x08\
-\xbf\x42\x02\xce\x4b\xc1\x38\xc7\x0c\x51\x3d\xa0\xd3\x36\x7f\x1a\
-\xfe\x8b\xd6\x81\x69\xa4\xe8\xcf\x6c\xbf\x20\xef\x65\xd5\xf0\xb5\
-\xa7\x24\xa3\x7c\x93\xce\xf4\x1a\x0b\x09\xf7\x69\xfd\xa6\x21\xa9\
-\x73\xb8\x3a\x01\xde\xdb\x4c\xc4\x3b\x08\xc8\x06\xce\x22\x70\x90\
-\x7b\xee\x33\x9a\x1b\xba\x6a\x7c\xdb\xc8\xa9\x6e\x1e\x48\xc3\xb7\
-\xb3\x73\x43\x15\x15\x51\x17\x33\xed\xfc\x99\xf0\x23\x2b\xc8\xff\
-\x9f\xdd\xb0\x7e\xa2\xf0\x49\xf0\xdc\xbd\x25\x02\xcd\x3d\xa5\xf1\
-\x1d\x0e\x09\xaf\xad\x7f\x9f\xeb\xb3\x29\x37\xc7\xe7\x4f\xc2\x8e\
-\xdc\xf7\xbc\x5d\xf9\xd9\xbe\xfb\x3e\x64\xfb\xed\xfd\xf0\xd6\xd7\
-\x32\xfa\xde\x35\x48\x9c\x2d\x80\x08\xd7\x7c\xf0\x34\x7f\x0f\x1e\
-\x1c\xf2\xfd\x24\xc4\xd5\x84\x11\x04\x53\x82\xbc\x28\xd4\xec\xea\
-\x26\x70\x70\xb9\x5c\x54\x56\x56\xc3\x46\x8d\x9a\xa2\x9e\x5e\x6b\
-\x6c\xdd\xba\x03\x5a\x5a\xf5\xc1\x3f\x16\xb7\xfd\x3a\x01\x44\x8f\
-\x03\xce\x0b\xc1\x78\xa7\xcc\x32\x87\xe9\xa2\x8a\xe0\x81\xe9\x09\
-\xbc\x8e\xb3\x52\xd2\xd9\xd0\x75\xa6\xa5\xa2\x7a\xcf\x6d\x28\xa7\
-\xd5\xa1\xe2\xba\xea\xca\x80\x13\x1c\x00\x6f\x6e\x00\x2c\xba\x01\
-\x58\x7a\xbb\x9c\x9c\xd3\xf0\x4c\x22\xfc\xdc\xb8\x61\x32\xbc\xfd\
-\xae\x50\x30\x63\x84\xc3\x1b\x1a\xbc\xb3\xf3\x32\xd4\xd2\x6a\xc1\
-\x36\xeb\xdc\x68\x5d\xe9\xa7\x18\x0b\x20\x2f\x17\xfa\xc1\x18\x8d\
-\x3a\x8a\xbd\x97\x3d\x2f\x1a\xbc\x15\x4b\x09\x48\x19\xb9\x4f\x18\
-\x71\xa4\xe0\x4d\xe6\x19\x61\x06\xb2\x1c\xe3\xbf\x2e\xe8\x16\x1e\
-\x1c\x6b\x19\x1e\x8a\xc6\x31\xd7\x78\x90\x38\x0b\x45\x3c\x99\x91\
-\x0a\x67\xdb\xc5\x81\x17\x23\x14\x89\x40\xd9\x07\x28\xb7\x82\x8b\
-\x4d\x5c\x95\xb1\xfd\xb4\x26\xd8\x6b\xbd\xbe\x70\xd8\x49\xb3\xd2\
-\x95\x07\x37\xe1\xc9\x93\x8f\xfe\xc1\xdc\xdf\xef\x65\x8d\x5b\x1e\
-\x86\x2b\x96\x8e\xc5\xb7\xf7\x81\xff\x35\x12\xe4\xde\xe7\x3c\x13\
-\x8c\x1d\x18\x93\xd0\x36\x24\xa9\xcf\xbc\x77\x91\xda\x0b\x32\x51\
-\x65\xfa\x29\x94\xd7\xed\x4e\x1e\x4f\xe5\x13\x44\x54\x95\x00\x5d\
-\xec\x00\xaf\xae\x03\x2c\xbc\xfe\x29\xf4\x4a\x9c\x67\x82\x49\xf0\
-\x65\x59\x03\x65\x7d\x8f\x4d\x81\xec\x3d\x93\x98\x82\x09\x23\x26\
-\x08\xc6\x8c\xd9\x8c\x2d\x5b\xd2\x92\x83\xa1\x2d\x22\x77\x5a\x62\
-\xfd\x54\x83\x41\xf4\x99\x4d\x6b\xae\x8a\x6a\xba\x68\xbf\xf4\x39\
-\xb2\xe1\xb3\x0c\xdc\x54\xf4\x61\xd1\x9d\xd8\x34\x1a\xfe\xb6\x94\
-\x48\x61\xdb\x2b\x17\x91\x86\xcf\xa2\x7b\x6f\x47\xbc\x6c\xc2\xf4\
-\x97\xca\x4f\x5c\x1f\x37\x79\xe2\x12\x6d\x1e\xda\x27\xca\xfa\xa6\
-\xee\x8b\x7e\xde\x46\x85\xa3\x03\xcd\xd0\x35\xcc\x12\xdd\xc2\x2b\
-\xe3\x1a\x6a\xc5\xdf\x72\xce\x2b\x53\x32\xfc\xcd\x7b\xc2\x22\x68\
-\xf8\x2c\x8b\xd7\x79\x66\xbd\x7b\x20\x93\xfe\x45\x01\xfc\x98\x92\
-\xfc\x20\xe5\x80\x81\xee\x81\x17\x65\x02\x62\xa3\x21\x2a\x0a\x99\
-\x07\x0f\xb0\x37\xf9\xf8\xfa\xc8\x00\x2e\xe9\x08\x78\x71\x26\xe0\
-\xf5\x79\x80\xb7\x17\x41\x49\xc4\x06\x78\x99\xe1\x0e\x21\xef\x0f\
-\xc1\x43\x42\x60\xee\x61\xc2\x51\x08\xcc\xda\xc8\x5c\xb9\x34\x81\
-\x49\xf2\x72\x03\x5c\x37\x4a\xb6\x64\xdc\xc8\x05\x68\x65\x35\x00\
-\x65\x64\x2a\x9e\xf3\xed\x7e\xda\xd1\x40\xf1\xb3\xf8\x8d\xa2\x5a\
-\x33\x34\xe8\x3a\x0b\x5b\x74\x5f\x80\xc6\x76\x2b\xb1\x8d\xe3\x06\
-\x34\x1d\xb0\x0d\xcd\x9d\xf7\xa2\xdd\xcc\x95\xa8\x37\xd5\x15\x35\
-\x47\x0c\x47\x55\x5b\x1b\xd4\xed\x6d\x8c\x63\x3d\xb4\x71\xe6\x05\
-\x4d\x74\xbb\xa8\xfd\xe1\xb7\xc0\x16\xb1\xf3\x1e\x99\x06\xcd\x8c\
-\x6a\x17\x33\x2d\xd2\xe2\xc5\xd4\x70\xcb\xdc\xaa\xc1\x57\xa6\x7d\
-\xf1\xae\xbf\x8f\x24\xd0\xf0\xbd\x0e\xc5\x04\x8f\x5f\xf1\x29\x7c\
-\x96\x99\x6b\xee\xe4\xbf\xba\xab\x11\x47\x42\x2e\xcc\x8b\x90\x4b\
-\xfe\x98\xa4\x14\x5c\x98\xde\x80\x57\x9c\xaf\xe5\xc7\x2f\xd1\x89\
-\x2c\x15\xea\xbe\x2e\xc5\x66\x82\x37\x45\xda\x41\xbd\xa2\xa6\x05\
-\x40\x4c\x0c\x52\x34\x3d\x3c\x8a\x4e\x93\x8f\xef\x73\x5c\x56\x81\
-\x17\x7e\x66\xc0\x8b\x71\x84\xb0\x27\x83\xa1\xd0\xcf\x19\x42\x68\
-\xf8\xf3\x07\xab\x62\x4f\xdb\x91\xa8\xa4\xa4\xce\xce\x2c\x1a\xf9\
-\xaf\x18\x0e\xa6\x03\x16\x6c\x53\x8c\xe0\x4d\x7b\xbd\x24\xfe\xcc\
-\x7e\x18\x67\xc5\x1d\x23\xb4\x9d\xfc\x17\xc3\x40\x5e\x37\x67\x40\
-\xaf\x28\xc0\xfb\xf8\x4f\xee\x0a\x99\x92\xbf\x8b\xe5\xd3\x4f\x17\
-\x28\x3f\x39\xf4\xae\x41\xd8\xee\xf4\x26\xfe\x1b\x53\x9a\xf3\x56\
-\x26\x19\xf1\x16\xc6\xb5\x09\x98\x1b\x63\x11\xb4\xfb\xe2\x5f\xb7\
-\x7f\x59\x12\x14\x3a\x70\x61\x40\x80\xdd\x5c\x3f\x5e\x97\xd9\xde\
-\x3c\xd3\xe9\xb7\x02\xf4\xa7\x5c\x0f\x6f\x38\xe1\x52\x62\x83\x91\
-\x57\xef\xbe\x2b\x68\xf5\x88\x04\x8d\x35\x90\xbb\x3b\x86\x9b\xb5\
-\x3e\x8c\xc1\x7a\x31\xbe\x31\x54\x80\x11\xae\xae\xe1\x55\x03\x3f\
-\xcb\x85\x8f\x77\x74\x21\x24\xcc\x06\x78\xf1\x83\x20\x85\x84\x8e\
-\x92\x78\xf6\x02\x81\x4b\x2f\x0d\x6c\xa4\xa9\xc7\x36\xeb\x68\x25\
-\x56\xe9\x5f\x35\x1f\x40\xdc\x2c\x72\xa8\xd2\xf7\xdd\x91\x36\xd3\
-\x6a\xf8\xfa\x06\x74\x84\x8d\x36\xe1\xac\x07\x01\x7a\x44\x54\x2f\
-\xc2\x03\x84\xe2\x50\x52\x53\x48\x44\x78\xf8\x06\xc1\xa7\x10\x21\
-\xb8\x90\x2f\x9f\xb0\xe0\xfc\x6a\x1f\x9d\x8d\xf7\x6f\xc2\xc8\xd3\
-\x58\x2d\xc3\xcf\x47\x42\xb7\x50\x81\xae\xf3\xcd\xcc\x62\xa1\x5e\
-\x56\x75\x02\xf8\xa4\xa9\xf9\x6f\x08\x03\xa4\xcc\x0a\x33\x7e\x4e\
-\x04\x28\x76\xd7\xd4\xcc\xa5\xa1\x5f\x52\x81\x67\xfe\xe6\xf0\x20\
-\xd6\x11\x42\xe9\x5d\x4e\xe0\xc7\x3b\x43\x46\xf4\x00\x48\xf2\xb5\
-\x87\xe4\xd3\x5d\xe1\xfd\x36\x0b\xf2\x7d\x2d\x01\xad\x1a\x71\x59\
-\xd1\xaf\xd4\x96\x59\x44\x75\x69\x30\xa7\x05\xe1\x22\xfd\x00\x3b\
-\xf7\x07\xdc\x43\x2a\x5a\x97\x7d\x00\x03\x23\xa0\xa4\xb0\x98\x8e\
-\xc0\x41\x99\xe4\xb7\xf0\x12\xad\xa3\x7a\x6c\xbd\x9a\x62\xbd\xf1\
-\x56\x1e\x73\x3e\x39\xa5\x7a\x01\xce\x64\x82\x6d\xd0\x1b\xe8\x1a\
-\x86\x14\xfb\x79\xfb\x63\x49\xe0\x7c\xc9\xf0\xf3\x4a\x75\x22\xd9\
-\xf0\x59\xfa\xde\x72\xbb\x7a\x51\x1b\x6e\xdd\xb4\x82\xbb\xb7\xac\
-\x21\xf0\x82\x15\x3c\x3a\x6a\x06\x19\x07\xdb\x40\xd1\x01\x63\xc0\
-\xfd\x84\xe5\xcd\x01\xfb\x37\x00\xd4\x95\xaf\xd4\x32\xa1\x4d\xd1\
-\x3e\xd2\x19\x41\xff\x9d\x08\xd6\x84\x4b\xea\xea\xc0\x0f\x0a\xfa\
-\xe7\x97\xe4\x16\xd6\xcf\x99\x74\x64\x4f\x40\xd7\x4d\x37\x91\x62\
-\xbe\xd7\x2f\x1c\xee\x65\xd0\xb0\x73\xab\x08\x50\x06\xbd\x7d\xa2\
-\xd9\xf0\x59\x36\x9d\x9a\xe5\x2b\x21\x40\xa1\xc7\x23\xd9\x97\x6c\
-\xf0\x0b\xbc\x8c\xe2\x06\x19\xac\x0c\x18\x2c\xbf\xde\x9f\x06\x2d\
-\x89\x3b\xb9\xc3\x67\xe9\x00\xda\xa8\x92\x16\x81\x4c\xa5\xd0\xe9\
-\x80\xcd\x49\x3a\xe8\x25\x9d\x12\xf6\x7d\x45\x68\xa4\xac\x0c\xaf\
-\xee\xdd\xfb\xf4\xd7\x67\x43\x07\x05\xd9\x6c\xbe\xfe\x96\x0d\x9f\
-\xd2\xe0\xdc\x93\xd7\x22\x01\x26\x9c\x8f\xaa\x24\x40\xff\x9b\x3e\
-\x55\xc3\x67\x09\x4b\xec\xee\x4f\x05\x08\x7b\xdb\x80\xb7\x3e\x98\
-\x53\x3a\x7d\x6e\xaf\x87\x7d\x95\xff\x7c\xd4\x07\xf6\x21\xc5\x41\
-\x66\x51\x1a\x0d\x7d\xab\x21\x69\xfa\x35\x06\x34\x27\xed\x7e\x59\
-\xa6\x22\xf0\x5c\x82\x0f\x61\x1b\x61\x6c\x6d\x9f\x35\x5c\xe7\x67\
-\xf8\xc8\x93\x22\x76\xa7\x97\xa6\x70\xf0\x9e\xc3\x51\x92\xc1\x53\
-\xac\x37\xdf\xca\x65\xee\xa6\xa3\x48\x80\x05\x77\x78\x9f\x9e\xfb\
-\x17\x42\x49\xd0\xc2\x9a\x04\x90\xef\x11\x58\x1c\xff\xdc\xe2\xbe\
-\x4b\xdf\xf1\x3e\xf6\x9c\x3d\x19\x6c\xf0\x2c\xa6\xdc\x5e\xa8\xa7\
-\x00\xc8\x94\x07\xfe\x4c\xdc\xfb\x48\xbb\xb9\x0d\x7e\xf4\xb0\xb6\
-\x54\x80\xca\x02\x4c\x94\x51\x50\xc4\xf6\x73\xfe\xc4\xaa\xe1\x53\
-\x4c\x3c\x02\x9e\x88\xc2\xa7\x6c\x0f\x7d\x28\x16\x20\x0d\x6c\x42\
-\xde\x57\x13\xfc\x47\x85\x2e\x01\x61\x7a\xed\x2f\x3d\xe8\xd9\x6a\
-\x5b\xdc\xd0\xa6\xab\x5f\xf6\x01\xf7\x5c\xc9\xe0\x7b\xc3\x4e\xd4\
-\x02\x4b\xf6\x4e\x4f\xa1\xd7\xff\x9e\x03\x33\x52\x01\xbe\x5d\x80\
-\x4d\x1a\x46\x6d\x70\x52\x50\x2a\x0e\x38\x19\x14\x42\x42\xff\x20\
-\x29\x80\xfa\xc5\xa4\xdc\x0a\x01\xce\x3e\x7b\x41\xc2\xe7\x43\x2f\
-\xbf\xc7\xe2\xc0\xf9\xb2\xd6\x41\xd1\x3a\xed\xaf\xde\xef\xda\x7a\
-\x6f\xf8\x30\xfd\x95\x45\x23\xf4\x97\xa3\x24\x83\x1a\x6c\x08\x92\
-\x14\xa0\x09\x54\x0c\xc7\x7a\xd0\x29\x66\xd2\xa5\x61\xff\x7b\x01\
-\x22\xf4\x7b\xf5\x17\x09\x40\x19\x73\x3f\xf9\x79\xb7\xcd\xb7\x12\
-\xc5\xc5\x7f\x36\x73\x4f\x5c\xfc\x53\xee\x66\x94\xc9\x0d\xb8\x7a\
-\xb9\x91\xd5\xad\x7b\x1d\x4c\x3c\x03\x9c\x0d\x57\xbf\xaf\x1a\x78\
-\x75\x38\xca\xef\xf0\xa3\xe1\x77\x83\x35\xa4\xc8\x17\x75\xfb\xae\
-\x94\x2e\x0f\xaf\x25\x6b\xf6\x09\x65\x6d\x27\xcc\xae\x10\x80\x32\
-\xf1\xe1\xab\xa2\xde\x1e\xbe\xbe\x66\xfb\x7c\x83\xc8\xf3\x3f\x53\
-\xf5\x68\x9c\xaf\xd9\xf4\x4b\xbc\x41\x6d\xff\x48\xfd\x9a\xc0\xab\
-\x32\x5c\x6f\x45\xa1\x0d\xb3\xb6\xb4\x29\xd8\xb0\x13\x31\xe4\xa4\
-\x02\xd4\x0e\x01\x3a\xd0\xe2\xd8\x76\xd5\x8e\x4a\x02\x50\x5c\x1e\
-\xa6\xc4\x1b\xfd\x7a\xe1\x82\x8b\xe9\x2a\xff\xb1\x6d\x56\xf8\x8d\
-\x36\x5e\xc6\x1b\x65\x44\x59\xee\x3f\xd2\x60\x45\xe8\x2f\x06\xcb\
-\x1f\x8f\xd0\x5f\xf6\x92\x04\x9c\xf7\x35\x12\x74\x6b\xe0\x82\x1c\
-\x90\xa5\x17\x5d\x22\xdd\x20\xa2\xf6\x08\xe0\x4a\x05\xe8\x7f\xe0\
-\x8a\x64\xf8\x02\xbb\x7b\xb1\x3e\x9c\xc3\xe7\xf8\x30\xe7\x52\x54\
-\xc3\xf1\x27\x92\x27\x59\xae\x4c\x99\xd2\x7e\x05\x7e\x06\xfe\xa4\
-\x76\x2b\x32\x27\xb6\x5d\x91\x34\xc1\x74\x79\xd4\x78\x93\x15\x81\
-\x63\x5b\x2f\xf7\x1d\x63\xbc\xc2\x67\xa4\xd1\x72\x1f\x6b\xad\xd1\
-\x69\xb2\x1c\x79\xb6\xeb\xb6\xa1\x54\x80\xda\x23\x00\x9d\x40\x8a\
-\x63\x6e\xc7\x8a\xc2\x9f\x10\xf8\xea\x75\xd3\x73\x77\x62\xe0\xd0\
-\x59\x14\x31\xfb\x72\x02\xcc\xb8\x8c\x72\x6e\x17\x72\x47\x76\x59\
-\x13\xfa\x05\x09\x24\x29\x1d\xd5\x6e\x75\x58\x3b\x83\x9d\x01\xb2\
-\x8d\x8f\x15\x80\xa2\x93\x50\x5c\xf1\x3b\x26\xdd\x22\xa6\x76\x09\
-\xb0\x55\xbe\xbe\x9a\x28\x7c\xe7\x07\x4f\x82\xe4\x8e\x5e\xc8\xab\
-\x08\x9f\x32\xe3\xca\x6b\x2a\x40\x39\x97\x84\xbd\xfb\x6c\xf7\x21\
-\xe1\x0a\x6b\x08\x5d\x30\xb6\xed\xef\x51\x1d\x8c\xb6\xfb\x29\x68\
-\x1d\xcd\x06\xcd\xe3\x58\x81\x7c\x45\xb3\xcf\x46\x2a\x40\xed\x12\
-\x60\x87\x9a\xb1\x29\x1a\x6e\xf2\xc0\x4a\xc1\xb3\x4c\xbf\x94\xff\
-\x49\x80\x72\x8c\x46\x1c\xa2\x25\x41\x2e\x1b\xfc\xb8\x76\xab\x1e\
-\x5b\x1b\x6f\xe5\x29\x69\x1d\xce\xac\x14\xba\x24\x32\xba\xec\x28\
-\x25\x23\x15\xa0\xf6\x84\xdf\x5d\xb4\x79\x53\x27\x13\x04\xa7\x65\
-\x08\xbb\x8f\xa5\x57\x0a\xff\xe0\xd9\xb2\xaa\xe1\xb3\x68\x8c\x39\
-\xf1\xd0\xc6\x78\xab\x8f\x6a\x93\xc3\xaf\x6a\x0c\x5d\x12\x46\x81\
-\x5e\x70\xa7\x74\x97\xb0\xda\x13\x3e\x9d\xdf\xff\x1e\xe4\x49\xad\
-\x7c\x8e\x1d\x42\xa7\xf9\xaf\xc1\xca\x4b\x00\x53\x8f\xf9\xc3\x81\
-\x33\xaf\x45\x02\x78\x9d\xcb\xae\x14\xfc\xb4\xcb\x2f\x60\xe0\x79\
-\x1e\x18\x9d\x7c\xf6\x55\xa1\xb3\x34\x70\x67\x8b\xff\x61\x52\x01\
-\x6a\x8f\x00\x83\x45\xa1\x74\x32\x40\x58\xe0\x88\x30\x75\x54\x12\
-\x58\x7a\xa1\x88\x8e\x5e\x25\xf0\xdb\x5f\x3c\xd8\x75\x2e\x86\x3c\
-\x02\xd2\x60\x18\x09\xbd\xcd\xc9\x27\xdf\x14\xba\x24\xf5\x67\xb0\
-\xa3\x79\x9a\x52\x01\x6a\x8f\x00\xbb\x80\xcb\x21\xcf\xf8\x9e\xe5\
-\x02\x50\x3a\xed\x8e\xae\x90\x80\xd2\xc1\x33\x48\xad\xe1\xb6\x6b\
-\x8a\x1a\xfb\x83\x48\x90\x1f\xfe\x63\x01\x64\x8d\xe9\x05\x4f\x4a\
-\x77\x0a\xad\x5d\x02\x3c\x06\xb3\xa6\x9f\xc2\xa7\x8c\x9e\x11\x54\
-\x49\x00\xf3\x6d\xbe\xd0\x72\x0d\xea\x68\x2e\x78\x68\xcd\x4c\xcb\
-\x32\x52\x5c\x19\x5c\x4f\xdd\x33\x80\x84\x9a\xfb\xd5\xe1\xab\x6f\
-\x62\x8b\xff\x8e\x52\x01\x6a\x59\xef\x1f\x8c\x53\x25\xcf\xff\x9e\
-\xf9\x9f\x24\xe8\x5b\x46\x82\x4f\xab\x10\xc0\x78\xc3\x0b\x2a\x00\
-\x45\x51\x7f\x55\x6a\x17\x8e\xdb\x23\x1b\x98\x82\xdd\xc0\x95\xdf\
-\x4a\x61\x69\xb8\x8a\xba\xbb\x1f\x09\xf9\xed\x67\x05\x50\xb4\xa3\
-\x17\x8c\x97\xee\x15\x5c\xbb\x04\x38\x06\xaa\x44\x00\x4f\x82\xbb\
-\xcc\x47\x58\xd6\x2d\xa0\x42\x82\x7e\x2b\x7c\xca\x05\xf0\x7c\xc5\
-\x86\xcf\xc2\x18\xad\x29\x6d\xad\x3c\xdb\x87\x48\x20\xa0\x22\x88\
-\x11\x98\xc8\x2d\x89\x56\x53\xdb\xc3\x63\x34\x8f\xa5\x55\x0a\xbf\
-\xe1\x41\x52\xfb\x57\xa4\x17\x9c\x23\x15\xa0\xf6\x84\xaf\x44\xf8\
-\x08\xb6\xe2\x05\x21\x2c\x9b\xf4\x1e\x12\x01\xf2\xe0\xd7\x41\xd9\
-\x44\x80\x22\xb0\xd8\xe3\x5b\x55\x00\x16\x0d\x9d\x25\xd1\xa4\x14\
-\x48\x97\x90\xa0\x02\x33\xd9\x05\x71\xea\xaa\xbb\x78\x8c\xc6\x4e\
-\x3e\xa8\x4c\xa2\x17\x2c\x20\xa8\x49\x05\xa8\x6d\xb5\xff\x39\x55\
-\x04\xa0\xec\xad\x97\x0a\x8b\xed\x62\xa1\xc7\x66\x7f\x30\xd9\x12\
-\x52\x93\x00\x14\x19\xc3\xd5\xef\x3b\xca\x4e\x0d\xae\x4e\x02\x16\
-\x65\x68\x8c\x3f\xcb\x5e\xc0\x3f\x93\x00\x5d\x45\x6b\x08\xa6\x56\
-\x23\x00\xc5\x93\x29\xe3\xce\xed\x7d\x9d\xd3\x6a\xed\xeb\xcf\x09\
-\xc0\xa2\xaf\x31\xd7\x8f\x84\x5d\x58\x35\xfc\x8e\x30\x0a\x45\x25\
-\xcd\x4f\xdc\xf4\xab\xcb\x75\x80\x67\xd0\xaa\x5a\x01\x3e\x5a\x9c\
-\x52\xe1\x9d\x08\x36\x7f\xf7\x21\x5d\xe1\x65\x7c\x58\xc3\x94\xcd\
-\x3b\xba\x05\xd8\x8e\x9c\xc8\x53\x6d\xbf\xec\x11\x09\xbc\xb8\x3a\
-\x09\x94\x9b\x2f\x7f\x6a\xcd\xb8\x26\x49\x0a\x60\x00\x9d\xbf\xeb\
-\x4e\x5f\x52\x01\xbe\xaf\x00\x0f\x44\x8f\x81\xf5\x50\x26\x0e\xbe\
-\xc8\xfc\xb4\x0a\xef\x78\x90\xf9\x9b\x1b\xe1\x96\x48\x89\x4f\x6a\
-\xea\x8b\x6f\xc8\x97\x4b\xc0\xcf\xe0\xf2\x7d\xef\x36\x8f\x9f\xb7\
-\xca\xc1\xd7\xbc\xef\xf4\x00\x79\xd3\x55\xcf\x88\x00\x42\x2a\x01\
-\xc7\x68\x75\x91\xb9\xe2\x4c\x5f\x56\x80\xfa\xe5\xc5\xbf\x41\x0d\
-\x13\x50\xe8\x32\x73\x2d\xf1\x76\x33\x2d\xc4\x4b\xde\xe9\x6a\xa7\
-\x5e\x84\xe1\xe2\xbd\x0b\x56\x8a\x17\x7e\x3c\x15\xcf\x0e\x3e\x21\
-\xde\x0d\x65\xad\x78\x51\x0c\x5d\xc2\x3e\x44\xbc\xd1\x83\xd5\xff\
-\x7a\x8c\xa1\x2e\x85\x6f\x4a\x67\x00\x81\x31\x09\x7e\x3b\x60\xeb\
-\x03\x4a\xe1\x47\x03\xcd\x32\xd9\xe0\x59\xbc\xa3\xcc\x43\xaa\x0a\
-\x50\x1d\x39\xcf\x14\xf2\xce\x9d\x6d\x13\x31\x76\xf6\x50\x9f\x66\
-\x36\xf3\x42\x74\x1a\x8f\xce\x6d\xcf\x0c\xa3\x17\x12\x8a\x17\x70\
-\x44\x88\x43\x7c\x2b\xee\x0d\xc4\xaf\x45\x56\x59\x09\x1b\xb4\x6f\
-\x8b\x6d\x46\x4f\x47\xdb\xa1\xd3\xd0\xa2\xa7\x33\x1a\xb6\xed\x8a\
-\x8d\x74\x5b\xa0\x82\x52\xfd\xaa\x5f\xef\x4b\xd7\x4d\x4a\x05\xf8\
-\x7c\xf8\xa2\x0d\x1e\xb8\x26\x0c\xea\x2d\x55\x44\xf7\x7b\x6d\xf2\
-\xab\x06\x2f\xc1\x07\x41\x16\x53\xf2\x35\x12\x88\xc8\x84\x14\x7c\
-\xc6\x04\xf8\xb8\xcb\xf0\x17\x2e\x6b\x8d\x93\xcf\x0d\xc7\x89\x67\
-\x86\xe1\x84\x53\x43\x71\xdc\xf1\x21\xe8\x72\x6c\x30\x8e\x3d\x3c\
-\x08\x47\x1f\x1c\x88\xa3\xf6\x0f\xc0\x5f\x3c\xfa\xe3\x88\x7d\x4e\
-\x38\x7c\x77\x5f\x1c\xba\xd3\x11\x87\x6c\x77\x40\xe7\xad\xf6\xe8\
-\xf0\x7b\x1f\x6c\xbb\x68\x16\xda\xfe\xe5\x89\x8e\xf7\xff\x46\xcd\
-\x33\xe7\x0a\x06\x5c\x0a\x2b\xf6\x0a\x47\xac\xca\xde\xc0\x22\xb4\
-\x58\xf8\x17\x82\xe9\x04\x04\x35\x03\xfa\x0b\x66\xfe\xaf\xb6\x9e\
-\xaf\x2b\x02\xf8\xa9\xb4\x51\xc0\x31\xbb\x9a\xe1\xce\x73\x6d\xf0\
-\x82\x9f\x45\xe9\x67\x04\xc0\x8c\x97\x6a\x91\xd5\x86\x9d\x05\x05\
-\xf8\x9a\x89\xc4\x58\xae\x0f\xde\x96\x0d\xc5\xa3\x0a\xd9\xb8\x5f\
-\x11\xf9\x5e\xf5\x9e\xfd\x7d\xd2\xbc\x6c\xc1\xb9\x01\x25\x8b\xc2\
-\xdd\xf0\x5b\x19\x7b\x73\x59\x92\xc3\x9d\x8b\xd9\x8e\xde\x57\x91\
-\xd2\x34\x24\x24\x12\x22\x9f\xe0\x9a\x88\x92\x77\xd5\x09\xe0\xf2\
-\xe0\x59\x02\x2c\x79\x16\x07\x83\x6e\xbe\x03\x7b\x2f\x04\xc5\x86\
-\xf4\x97\xec\x2b\x15\xa0\xfa\xf0\xe9\xd6\x2f\xd8\x7c\xa5\x16\x76\
-\x7a\x6e\x52\x41\xef\xa7\x26\x6f\x5d\x12\xcd\xa2\x97\xc6\xb5\xf5\
-\xdb\x17\xd3\x8e\x77\x26\xd2\x22\xec\x5a\x84\xe5\x2b\x22\x80\x20\
-\xf4\x91\x11\x8f\x04\x2e\xc4\x0c\x78\x8a\x49\x8c\x3f\x06\xc8\xf8\
-\xe1\x59\xf9\x64\x3c\xa0\x28\xa0\x81\x4b\xf2\xf1\x88\xfa\x43\x1e\
-\xaf\xeb\xc7\x7b\xbe\x36\x69\x8b\xc3\xdd\x84\xdf\x12\xfc\x82\xb0\
-\x99\xb9\x83\x79\x27\xfd\xd9\xe0\x29\x7a\x41\x41\x31\x34\x7c\x42\
-\x91\x67\x35\xe1\x2f\x0e\xcb\x4f\x80\xa0\xd0\x5c\x98\x23\xcc\x81\
-\xd9\xa5\x6f\xc1\xe1\x00\x82\xac\x32\xfd\x45\xb7\x49\x05\xa8\x5e\
-\x00\xba\x32\x18\xdb\xde\x6f\x51\x49\x80\x6a\xe0\xf7\x4e\x6e\x15\
-\xbb\x3c\x58\xcf\xfb\xe9\x85\x06\xa1\x78\x44\x21\xaf\x6a\xd8\x55\
-\xe0\xa7\x9e\xd7\xe3\xdd\xf7\xb5\x41\xca\x59\x7f\x47\xde\xb7\x84\
-\xef\x16\xb8\x26\xcc\xd1\xfb\xef\x0c\xc9\xf0\x0d\x03\x03\xe3\xc4\
-\xe1\xa3\x62\x44\x62\x42\xd5\xf0\x37\x86\x17\xa7\x71\x82\x82\x33\
-\x95\xce\x3e\x8a\xa2\x7d\x8c\x22\xfa\x9e\x10\x02\x47\x86\xfe\xa2\
-\x7b\xa5\x02\x54\x2f\xc0\x19\x86\xcb\xa0\xf9\x9d\xca\x02\x74\x7c\
-\x6e\x52\x6c\x9f\xd8\x2a\x7a\x71\xb0\x9e\x8f\xf7\x95\x86\x91\x45\
-\x87\x94\x3e\x7e\x21\xf0\x4f\x1c\xa8\x97\x19\x75\xd3\x3c\x86\x0d\
-\x9f\xb2\x31\x78\x4c\xfc\xd7\x04\x3f\x2f\x6c\x46\xde\x60\x9f\xe3\
-\x95\xee\x7a\x4a\xcb\x87\x01\x09\x6c\xf8\x14\xfd\x88\x94\x20\xc9\
-\xf0\x77\x86\x97\xe5\xc9\x06\x85\x24\x43\x50\x10\x26\xa4\xf4\x0d\
-\x4b\x7a\xe4\xec\xab\xb7\xfc\x75\x22\x8c\x89\x65\x2b\x83\x9e\x52\
-\x01\xaa\x17\x60\x8f\x68\xc3\xa7\xfa\x5c\xb4\xd8\xa3\x8d\x13\x8f\
-\x68\xe7\xfa\x5f\x6a\x18\x5d\x72\xb0\x5e\xd1\x57\x07\x2e\x01\xff\
-\xa0\x4a\x8c\xbf\x77\xe7\x2c\xc9\xf0\xef\xfa\xd9\xbe\xfc\x9a\xf0\
-\x5d\x83\xd6\x84\x3b\xde\xbf\x92\x5e\x35\xfc\xd6\x81\x7e\xc9\x24\
-\x74\xa1\xa4\x00\x76\x91\x59\x81\x6c\xf8\xee\xe1\x42\x7e\xfd\xa0\
-\xf0\x48\x1a\xbe\x6a\xf0\x83\x22\x4c\xb3\x2c\x26\xa0\x20\xd5\xb2\
-\x70\xc5\xfa\xbe\xa8\xd0\x40\xb4\xdf\x6f\xa9\x78\xc3\x08\x39\xa9\
-\x00\xd5\x6f\x9b\x5e\xd8\xc3\x1a\xf0\xdc\x06\xe6\x4d\xf6\x2e\x05\
-\x7f\x12\x66\xf1\xb7\x86\x9f\x73\xb2\x31\xcf\x9b\xd7\xad\x54\x32\
-\x7c\xca\xe9\xc0\xbe\x3e\x9f\x7d\xd6\x87\xce\xc8\x77\xe6\x1d\xf7\
-\xab\x1a\x3c\xc5\x24\x80\x97\x42\x02\x17\x48\x86\x4f\x99\x19\x51\
-\x10\xc7\x0a\xa0\x13\x1c\x13\x40\xc3\xa7\x2c\x7d\xbc\x26\x84\x86\
-\x2f\xc9\x8b\x60\x33\x1c\xda\x4f\x9d\x2d\x09\x62\xfe\x3f\xf7\x0a\
-\xaa\x4b\xfd\x00\xf7\xb8\x5c\xc0\xb4\x58\x10\x86\x9c\x86\xe0\xe3\
-\x33\x98\xb7\xc9\x9b\x64\x7d\x84\xfb\x15\x33\xbf\x22\xfc\x0f\xc9\
-\x57\x5a\x3e\xac\x1a\x3c\xcb\xfa\x90\x71\x49\x35\x3e\xeb\x83\x7e\
-\x8f\xe8\xeb\x7d\x25\xad\xba\xf0\xcd\xfd\x7d\x52\x49\xd8\xa5\x55\
-\xc3\xa7\x6c\x8f\x28\x2b\xa0\xe1\x5b\x86\x26\xf9\xb0\xe1\x53\x96\
-\x4f\x33\xf6\x7e\xb9\x59\xfb\x9a\xf0\x6e\xab\x20\x0c\x34\xf1\xc3\
-\xc7\xe6\x91\xfc\x64\x8b\x3c\x2a\x82\xcf\x85\x96\x68\xde\x5a\x91\
-\x9d\x85\xb4\xea\x7b\x37\x0d\xc5\xd3\xe9\x74\xea\xaa\x00\x4b\xe9\
-\x1d\x12\x78\xb3\xbc\x49\x97\x74\x1f\x7c\xe9\x86\x4b\x07\xa6\x02\
-\x3f\x68\x95\x4c\x00\xdf\x5d\xe1\x71\x75\xe1\x0b\x0f\xd6\x7b\x11\
-\x72\xa7\x7d\x72\x4d\xe1\xdf\xf5\xb5\x7d\x56\x5d\xf0\xf3\xc3\x67\
-\x14\x38\xf3\xfe\xaa\xf6\xae\xa7\xb4\xbf\xb6\x2b\x9d\x7b\xf7\x98\
-\x3f\x44\xc6\xa7\x55\x0d\x9f\x13\xf1\x24\x83\x86\x3f\x20\xe4\xb5\
-\xbf\x64\xf8\x1a\xb7\xce\xe7\xae\x19\x02\xc2\xdd\x7d\x74\x7c\x22\
-\x5c\x14\xee\x15\x1c\xd6\x7d\x8c\x37\x5a\x22\x45\x78\xc3\x28\xf7\
-\xd5\x09\xc3\x77\x01\xee\x7a\xb8\x75\xa6\x16\xb6\x6c\x2a\x87\xe2\
-\xce\x28\xd3\xef\xf0\xd9\xa9\x11\x22\xc5\x9d\x5c\x74\xc7\x55\xd5\
-\xba\x28\xc0\x1d\x83\xe6\x80\xc2\xac\x4f\xed\xfa\xf4\x10\xf0\xf3\
-\x9a\x0a\x42\x2a\x02\xe5\xfa\x7c\x4e\xec\xfb\x5d\x0a\x0f\x69\x0d\
-\x9f\x86\x5f\x74\x44\x35\x98\xf7\xc0\x3a\xbf\xa6\xf0\x29\xa7\x02\
-\xfa\xfd\xa3\xf8\x9f\x1a\xb4\x2a\x92\xdc\xf5\xaf\x6b\x0a\xbf\xf3\
-\x8d\xfd\x6f\xc0\xab\xef\x47\xf0\x72\x44\xf0\x72\x2a\x83\x4b\xeb\
-\x83\x20\x24\x3c\x9a\x15\x40\x2d\xf2\x69\x98\x5b\xd8\xbb\x08\x12\
-\x7a\xa9\xa4\x00\x43\x56\xf5\x8d\x20\x02\xe0\xba\xc1\x72\xa9\xbb\
-\x7b\x19\xe6\x06\x3b\xcb\x06\x26\xcd\x50\xf4\x2d\xbb\xd4\x22\x97\
-\x15\x81\xa5\xf8\x8a\x11\xf6\xb5\xaa\xd8\x33\x79\xda\x7f\xb9\x49\
-\xe7\xd5\x7a\x5a\xda\x68\x7f\xec\x0a\xfb\x98\x59\x5b\xd7\xea\x00\
-\xb4\xef\x5d\xb8\x6e\x89\x38\xfc\x34\x48\xc3\x27\xc0\xc3\x48\x48\
-\x4c\x3c\x09\x37\xf7\x4f\x85\x52\x56\x02\xca\x89\x59\x4c\x46\xd4\
-\x8e\xfa\x37\xef\xf3\x6c\xde\x7e\x2e\x7c\xca\xba\xb0\x71\xcf\x2b\
-\xee\xfa\xb0\xe9\x1f\x86\xfa\x1e\xf3\x25\x21\x0b\x6b\x0a\xdf\xe6\
-\xf6\x5f\xef\x18\xaf\xbe\xef\xcb\xc3\xaf\xc2\x09\xb7\x24\xf0\xbf\
-\xe3\xa7\x1d\x96\x78\x8b\x04\x9e\x2f\x19\x3e\x13\xf8\x10\x57\x0e\
-\x97\xcf\xa2\x02\x50\xb6\xda\x37\xe1\xed\xe8\xd9\xa2\xcc\x7f\x90\
-\x82\x5f\xd4\x30\x78\x9b\xb5\xb1\x61\x40\x55\x09\x3e\x5e\x6a\x85\
-\xc3\xac\x0d\xd8\xd0\x8e\xfe\x27\x87\x58\x93\xd7\x64\xfa\xfd\x03\
-\x2e\xf3\xd0\x2d\x0b\x51\xd5\xc0\x88\xfe\xe5\x8d\xba\x26\x40\x7f\
-\xfa\x4b\x5c\xdc\xc7\x60\xb6\x0f\xa4\x60\x38\x08\x09\x28\x26\xeb\
-\xce\x16\x08\x22\x12\x14\x4a\x4a\x40\xd9\x3f\x8d\x53\x74\x7e\x4f\
-\x33\xbf\x7b\xde\x5d\x13\xaa\x0b\xff\xb6\x6f\xf7\xc4\x8a\xbb\x3e\
-\x70\x55\x54\x5f\xef\xbf\x53\x6b\x0a\x9e\xd2\xe3\xce\xe9\x5c\xce\
-\xfe\x7e\x69\xd5\x86\x4f\x60\x3c\xec\xcb\x1a\x4d\x33\x0e\x30\x1c\
-\x62\x98\xac\xb7\x65\x6b\x68\xf3\x9d\x1e\x48\x69\x46\xff\xbb\xc3\
-\x1d\x7f\x99\x65\x1b\x4b\xc2\x2f\x13\x49\x30\x98\x93\x47\x04\xc8\
-\x26\xe0\xbd\x01\x4a\x3e\x51\x43\x01\x1f\x8d\x96\x89\x2a\x3c\xd1\
-\xfc\x99\xa4\x04\xd9\xa7\xac\x70\x56\xef\xa1\xa8\x28\x5b\x8f\xad\
-\x20\xb6\xff\x86\xcf\xad\x3e\x2d\xf2\x9b\x3b\x0c\x14\x85\x4f\x69\
-\xe9\xe2\x4a\xff\xc1\xa7\xae\x09\x20\xea\x0d\xfc\x63\xa3\x1d\x66\
-\xdd\xe3\xbe\x91\x08\x5f\xc4\x9b\xdb\xe0\x73\x6b\x13\x3c\x3e\x38\
-\x1d\x72\xaa\x4a\x50\x51\x2a\xac\xd1\x88\xba\x7d\xa3\x63\x30\x09\
-\xbe\x8c\x15\xe0\xaf\xc0\x01\x3e\x0b\xc2\x67\x92\xbb\xfe\xe8\x67\
-\xef\x7a\x8a\xdd\xdd\xf3\x1f\xb8\xfb\x07\x24\xd7\x14\xbe\xca\x72\
-\xab\x44\x43\xa7\x86\xa9\x2d\x1c\x35\x50\x8c\xd0\x60\xf6\x48\x6f\
-\x12\x7c\x3e\x0d\x9f\xc5\x7a\xe5\xac\xcc\x95\x43\xb9\xb4\x2e\x80\
-\x9b\x1c\x1b\xfa\x53\x01\x28\x57\x9d\xd4\x7c\x89\x04\x42\x42\xc9\
-\xf3\xb9\xca\x3e\x82\xab\x46\x85\xac\x04\x39\x67\x2c\x84\x2b\x9d\
-\x96\xa2\x89\x56\x3b\x14\x0d\x88\x01\xec\x24\xa8\x7c\xc5\xe7\xb6\
-\x96\x23\x23\x83\x23\x1e\x26\x60\xdf\x98\x8c\xcc\x46\x17\x1f\x16\
-\x28\xfc\xf6\x3b\xfd\x87\xb8\xba\x26\x00\x1d\x8a\x15\xae\xde\x3e\
-\x0e\xb7\x7a\x4d\x2c\x4b\xf1\xd1\xe1\x55\x29\x05\x0a\xfd\x76\x41\
-\xfa\xed\xcd\x90\x7c\x70\x26\x64\xd6\x24\x01\xe5\xf0\x7c\xc5\xd4\
-\xab\x67\x4d\x79\xe4\xf1\x90\xb3\x30\x68\x81\x8f\xe3\x83\x2b\xaf\
-\x68\xc0\x0e\x77\xaf\x7c\x74\xb8\x76\x3e\xd3\xfe\xf2\x99\xe7\x76\
-\x67\x8e\xc7\xd9\x1d\x3d\x14\xd9\x6b\xbf\x57\x70\xcf\xbd\x7b\xfd\
-\x7b\x6c\xdb\xce\x93\xdb\x3f\x38\xba\xba\xe0\xe5\x36\xdb\xe4\xeb\
-\xf6\x6b\xf4\x86\x0d\xde\xa0\xb7\xba\xd0\xb0\x8f\xba\x90\xfd\xb3\
-\xfe\x40\x83\x2c\xdd\xd5\x6b\x93\x25\x25\x68\xbd\x71\x03\x4e\xee\
-\xa3\x80\xbf\x3b\x33\xb8\xd1\x56\xff\x2d\x2b\xc1\x05\x27\xcd\x87\
-\x44\x00\x3e\x2d\x0d\xa2\x87\x33\xa9\xd9\x3b\xb5\x42\x59\x09\xf2\
-\xcf\xb5\x13\xac\xed\xbf\x01\xc7\x58\x4d\x45\x25\x39\x51\xd7\xf1\
-\x6b\x3a\xac\xfc\x99\xcf\x8b\x4a\x82\xda\xd6\x63\xd0\x72\xd7\x89\
-\x8f\xca\xa1\x19\xa5\x04\x94\x9b\x30\x87\x6d\x65\xc8\xd5\xb5\x4a\
-\xe0\xb3\x65\x1b\x47\xe3\x16\x2f\x37\x11\xd7\xce\xdb\x85\x0b\xc3\
-\x98\x4c\x56\x82\x7c\x5f\x08\x20\x8f\x02\x24\xbc\x3e\x3c\x0b\x52\
-\x6a\x12\xc0\xd3\x0d\xf2\x56\x4d\x82\x80\x4e\x2e\x72\xc1\x5d\xa6\
-\x2d\x88\xef\x3a\x75\x41\x0e\xa1\x8c\x80\x35\xd1\x74\xce\xf0\x7f\
-\x14\xfb\xdc\xdd\xbd\x51\xd3\x59\x57\xa8\xd7\x4b\x0d\xb5\x2c\x94\
-\x51\xad\xb9\x02\xca\xab\x70\x45\x67\x1b\x30\x1c\x40\x45\x0d\x19\
-\xd4\x68\xa1\x88\x3a\x1d\x55\xd0\xa0\x8f\x06\x36\x1e\x6c\x87\xcd\
-\x36\xef\x14\x09\x50\xbf\x57\x1f\xe4\x90\xaf\xeb\x63\x0a\x38\xa3\
-\x9b\x12\xb2\x02\x50\x4e\xd8\x6b\x45\x44\x0e\x85\x0f\x54\x02\x4a\
-\xdc\x04\xd9\xe0\x92\x33\xfa\x69\x54\x82\xc2\x4b\xe6\xa5\x1b\x06\
-\xae\x2f\x59\xe3\xb4\x1b\xcd\xb5\xad\xd8\xba\xc1\x55\xf6\xc0\x28\
-\x71\x91\x4f\x0f\xa5\xc8\xaa\x27\xd7\x0c\x5b\xb6\xde\x89\xcc\xaf\
-\xe1\xc2\xdd\x57\x03\x72\x56\xde\x7f\x94\xde\xe8\xef\xd0\x62\xb9\
-\x21\x2e\xec\xee\xea\x75\x4e\x80\x93\x0b\x56\x8f\xa8\x10\x80\xb2\
-\xe7\xd0\xb8\xec\xbc\x20\x95\x60\xb1\x04\xc2\x90\xfd\x10\x2f\x96\
-\xe0\xdd\xd1\x39\xf0\x44\x22\xf4\x82\xd5\x93\x21\xa0\xdb\x28\x6e\
-\x88\x8c\xb3\x4c\x31\x0c\x96\x41\x4a\x8b\xa1\xf6\xaf\xab\x0b\xdc\
-\xda\x6d\x3e\x5a\x0c\x9f\x80\x46\x3d\x1c\xb1\xf1\xb0\x5e\x08\x1e\
-\x0e\x08\xfb\xec\x11\xf6\xf4\x41\xd8\x65\x87\x32\x0b\x3b\xa0\x72\
-\x5b\x75\x94\x53\xe6\x4a\x8e\xeb\x17\x88\xb7\xbe\xa5\x5b\xdd\x9e\
-\x13\xef\x1e\x26\xfa\x37\x56\x08\x15\xa3\x26\xa8\xd4\xbe\x03\x5b\
-\x8c\xff\x45\x8f\xb1\x33\x6e\x02\xf8\x9b\x95\x4e\x25\x09\x0e\xf5\
-\xd6\x49\x88\x1a\x02\xd9\xac\x04\x84\x0f\xaf\x96\xa9\xf2\x84\xd7\
-\x8c\x4a\x4b\x2e\x99\xf1\xb7\x0d\xdd\xf2\x66\xbb\xf3\x51\x9c\xd4\
-\x79\x0e\x1a\x36\x34\x66\xa7\xb0\xd1\x93\x48\x72\x64\x38\xca\x68\
-\xd2\x64\x25\x76\xea\x1c\x82\xcc\xfc\xd8\x32\x58\xf1\x12\xbd\xee\
-\x86\x17\x12\x70\xcb\xd9\xbb\xd8\xb0\x45\x2b\xfa\xf5\x7b\xea\x62\
-\x33\xd0\x6b\xce\x32\xe7\x4a\x02\xb0\x04\x5f\xb7\xf0\x23\x02\x7c\
-\x2c\x09\x82\x68\xb1\x00\x78\x67\x33\x14\xac\x9c\x0b\xf7\xba\x4f\
-\xe0\x44\x28\x0c\x95\x29\x62\x43\x97\x44\x61\x80\x06\x5a\xbb\xce\
-\xc5\x76\xc3\xc6\x8b\xc2\x6e\x62\x62\x81\x2a\x8d\x9b\x20\x7d\x6e\
-\x56\x33\xd1\xa3\x4c\x7c\xe7\xd0\x49\x22\x61\xb4\x63\x4a\x7c\x8c\
-\xeb\x2c\x71\x1d\x85\xa9\xe1\x94\x92\xf6\xe2\x23\x5f\x27\x11\x42\
-\xc4\xb3\x9a\x4c\x25\xef\x58\x55\x05\xee\x9b\x3e\xcd\xd5\x71\x59\
-\xe7\xe6\x9f\x44\xe8\xde\x34\x37\x7c\x08\xa4\x4b\x48\x80\xd1\x23\
-\x98\xe4\x7c\x2f\x9d\x58\xfe\x15\x93\xd2\x7d\x23\x77\x24\x52\x09\
-\x28\x8b\xed\xfe\x40\x0b\xdd\x49\xd8\xc3\xe8\x2e\x8e\x68\x5f\x8c\
-\x1d\xba\x45\xf1\x61\xe9\xd3\x22\x1a\xbe\xcc\xaa\x97\x1f\x68\xf8\
-\x7f\x9e\xbb\x8b\x4d\x9a\xe9\xd3\x8b\x26\x4b\x9e\xc7\x54\x97\x04\
-\x08\x9c\x36\x7f\x40\xb5\x02\x50\x8e\xfc\x35\xec\x39\x3f\x44\x36\
-\x3e\xee\x04\x04\x53\x01\x96\xad\x01\x3f\xdb\x65\x20\x24\x20\xa5\
-\xdb\x12\x28\xea\xb8\x10\xde\x58\xcc\x65\x22\x9a\x3a\x32\xe9\x4a\
-\x2d\x18\x04\x25\x06\x19\x2e\xf7\x99\x78\xd7\xaf\x75\x84\x79\xe2\
-\xa0\xe8\x49\x23\x3d\x68\x97\x2c\xa1\xb9\x38\x28\xe6\x07\xfe\x6e\
-\x0a\x84\x99\x74\xeb\x39\x7d\x55\x05\xfc\xa5\x55\x23\xdc\x68\x63\
-\x80\x9b\xbb\xe9\x0a\xef\xf7\x67\xde\x4b\x4a\x40\x2b\x8a\x09\x6e\
-\x0a\xfe\x85\x67\x8d\xdf\x1c\x18\xbb\x27\x8a\x95\x60\xa0\x99\x6f\
-\xc1\x48\x4b\x44\x4b\xfb\xe7\x2f\x49\xf0\x1f\x69\xf8\x14\xb5\xa5\
-\x51\x1f\x69\xf8\xda\xcd\x0d\xd8\x3d\x8e\x9a\x7f\xb7\x9e\x40\xf2\
-\xea\x26\x3e\xe0\x60\x9f\x78\x20\x43\xfd\x07\x9e\x38\x52\x30\x69\
-\xb6\x63\x8d\x02\x50\xfe\xdc\x3f\x85\x1f\x7e\xad\xd5\xed\x75\x6b\
-\x18\x9e\xed\x12\x88\xb7\x59\x08\x01\x5d\x7f\x85\xbb\x9d\xa7\xc0\
-\xdd\x0e\x63\xc1\xcf\x72\x28\x3c\x6e\xd7\x1f\x0a\x2d\x06\x02\x2a\
-\x37\xac\xd8\xb4\x99\xa9\x45\x92\xcb\x88\x0f\xd7\x48\x90\xe5\x30\
-\xd8\xbe\xb1\x0a\xba\x99\x35\xc2\x33\x76\x9c\xb2\x2a\x12\x60\xf4\
-\x30\xc8\x4e\x5d\xa3\xe5\x7b\x68\xdc\x8e\x30\x2a\xc0\xe8\x0e\xfc\
-\xc7\x96\xce\xf9\x41\x24\xf4\x12\x36\x7c\x91\x00\xb3\xef\xa0\xb6\
-\x9e\x21\x7b\xcc\xbd\xee\x7f\x3d\x16\x20\xde\xbd\x7b\x66\xf9\xc1\
-\xc5\x0c\x6a\x69\x59\x60\x8b\x16\x0e\xa8\xa0\x20\x3a\x41\x3b\x94\
-\xa0\xfc\x03\x3e\x18\x1d\x5a\x0c\xf7\x18\xd4\x06\xef\x25\xad\xc9\
-\x7b\x8a\x5e\x58\x13\x87\x5e\x2d\xe2\x99\x8d\x94\x7b\x4d\x43\xae\
-\x89\xe6\xed\x45\xe1\xbf\xa7\x13\x3c\x6b\x69\x69\x47\x8f\xd5\x1b\
-\x45\x97\xa6\xd1\xdf\x5b\x43\x81\x8b\x03\xf4\xf5\xd0\xa5\x8d\x7d\
-\xe9\x62\xab\xee\x2f\xf7\xf7\xe8\x18\x7a\xc9\xb1\x45\xec\x83\x81\
-\x1a\xa9\x61\x23\x95\x22\x0e\x8c\xdc\x16\x60\x3a\xea\xfd\x03\x12\
-\x78\x99\x64\xf8\x22\x86\xba\xb3\x67\x32\xea\x7c\xf5\x60\x10\x0d\
-\x51\xfc\xec\xa2\x73\xf1\x1d\xc5\x56\xd2\xe2\xf1\x06\x1d\xb6\xe4\
-\x70\x64\x50\x53\xb3\x0d\x9a\x9a\xfe\x82\xd6\xd6\x0b\xd0\xcd\x2d\
-\x1c\x47\x8d\xba\x8a\x9c\xf2\x89\x0d\x93\x7f\xd0\x06\xd1\xe8\x34\
-\xae\x2d\x0e\x76\xb5\x2c\x3c\x13\x38\x3f\xb4\x9a\xf0\x8b\xa6\x06\
-\x8f\x0e\x00\x2f\x03\x34\x1b\xca\xc9\xab\x29\x7c\xf3\xbe\x80\x32\
-\xe5\x3b\x78\x4f\xad\x03\x8f\x3d\x2a\xc2\x48\xda\x6e\xa7\xbf\x7f\
-\x13\x65\x23\x1c\x6c\x3c\x1f\x47\x99\xac\xae\x84\x8b\xf9\xea\x57\
-\x8a\x2e\x57\x7d\x60\x46\xa0\x1f\x2c\x4a\x0c\x83\x15\x29\x39\x15\
-\x02\xb4\xe9\x2f\xea\x45\xfc\xe2\x68\xa0\xb8\xf8\x99\x2a\xee\x6d\
-\x12\x56\x54\x7e\x68\x9b\x53\x9d\x3c\x3f\x9a\x76\x41\x68\x35\x08\
-\x65\x4c\x47\xa3\x99\xb9\x0b\x5a\x5a\xba\x89\xe8\xd2\x65\x6e\x19\
-\x15\x80\x62\x6a\x64\x57\xa9\x97\xe9\x3b\x7e\x10\xa3\x64\xe5\xb9\
-\xe8\xec\x66\xc9\x22\xfc\xdd\x73\xb8\x4f\xb2\xd0\x53\x40\xc3\x4f\
-\x14\x7a\x64\x74\xba\xd6\x3d\x9e\x86\x4f\x10\x58\x0c\x02\x61\x4d\
-\x02\x68\xea\x57\x0c\xb9\x72\xeb\x50\xfd\x87\x8a\x30\x82\x96\xba\
-\x72\x5c\x05\xec\xa2\x33\xa4\x92\x00\x13\x2d\x36\xa4\xd9\x0f\x3c\
-\x14\x0c\x03\xbc\xb0\x82\xc1\x47\xf2\x60\xd0\x1e\x92\x9f\x68\x3c\
-\xc1\xe9\x6b\x04\x58\x29\x3a\xcc\x71\xd0\x7a\x84\x9d\xb9\x08\x8b\
-\x83\x11\xa6\xdd\x44\x70\xf5\x21\xff\x8d\x40\x20\x01\xb3\x70\xc7\
-\xdc\x7a\x61\xda\x79\x5e\x06\x15\xa0\x43\x87\xe9\xa2\xf0\x27\xba\
-\x5c\xcf\xf3\x73\x11\x1d\x70\x28\xa8\x5a\xd1\xf8\x0e\x1f\xc0\x2a\
-\x8d\x26\xf5\xb0\xff\x74\x13\x1c\xfc\x9b\x79\xce\x90\xb9\xe6\xd9\
-\xce\x73\xdb\xbe\x9f\xb4\xa1\x9b\xbf\x6f\xee\xc6\x50\xad\x13\x6d\
-\xb3\xc4\xe1\x23\x78\x1a\xe4\xd6\x14\xbe\xb1\x6d\xc5\x19\x84\xbd\
-\xeb\xe8\xd6\x38\x8c\x78\x53\xea\x94\xa6\x2a\xad\xd0\xd9\x78\x81\
-\x48\x80\xf1\x16\xab\xd2\xb7\x38\x1f\x2d\xe3\x0c\xd8\xff\xa6\x92\
-\x04\x66\xa3\xd8\xdd\xcb\xe5\x3e\x2b\x80\xf8\x28\xd3\x12\x70\xde\
-\x58\x7e\x9e\x5f\x55\x3c\x05\x7c\xd8\x51\x90\x0c\x1b\xdf\x06\xc3\
-\xe2\xa7\x71\x30\x2d\x92\x4f\x44\x78\xdf\xdc\x61\x47\x30\x95\xc0\
-\xcd\x35\x2c\x37\xf1\xb7\x89\x69\xb8\xb4\x1f\x5a\x6a\x89\xea\x02\
-\x17\xbe\xe7\x9e\x7a\xb4\x6e\x61\x3c\x03\xf0\x97\x37\x95\x71\x7c\
-\x2a\xef\xcf\x39\xa0\x5f\x5c\x11\x3e\x81\xf1\x34\x48\xad\x56\x80\
-\x01\x80\xf5\xca\xcf\x1c\xbc\x52\xe7\x57\xf3\x94\x0f\xef\x5e\x92\
-\xe7\xd6\xc3\xae\xba\xc3\x71\xb8\xe9\x6f\xa2\x96\x80\x75\x7f\xaf\
-\xa8\x8a\xf0\x9d\x76\x23\xc8\x8b\xd6\x20\x1c\xff\xe2\x84\x10\xf1\
-\x71\xae\x61\xd0\xdc\x12\x61\x6b\x06\x0d\x1c\xc1\xa3\x14\x19\x0f\
-\xbe\x90\xf1\x2c\x2b\x61\xbc\xf0\x23\xb3\x1f\xf3\x09\xb9\x1c\x2f\
-\x7c\xcf\xf1\x12\x66\x2a\x7a\xf0\x43\xd4\xb6\xe7\xdc\x6f\x35\xcf\
-\xef\xda\xcd\xd9\x6b\xee\xd3\xf0\x29\x57\x87\x59\x61\x63\x25\x79\
-\xb6\xe2\x41\x7b\xa5\x14\xbe\xc3\x48\x20\xf6\xb8\x58\x29\xfc\x32\
-\xab\x10\x55\x9e\x64\xf0\x2c\x1c\x77\xfd\x84\xea\x04\xd0\x35\x07\
-\x76\xda\x95\xd1\x4f\xb3\xac\xab\xbc\x0f\xa2\xd8\xb0\x81\x29\xce\
-\xef\xb5\x16\xd7\x0f\x3e\x84\x4c\xbf\x7d\x7c\x91\x00\xe4\x71\x2d\
-\x7e\x8c\xdb\x7f\xd5\x8c\x20\xf2\x6a\x29\x3e\xa9\x52\xf4\x8d\x0d\
-\x9a\x34\x47\xa7\x49\xcb\x71\xc3\xd5\xe7\x9f\x66\xb6\x86\x10\x78\
-\x28\xf4\xba\x8e\xaf\xbd\x4e\xe1\x63\xcf\x83\xf8\x6c\xe7\xce\x9c\
-\xd7\xcb\x7a\xf6\xbc\x9e\x60\xa7\x7f\x25\xdb\xb5\x4b\x6c\xde\x5c\
-\x87\xb2\x92\x45\x7d\xf1\xf8\x80\x76\xd8\x49\x5b\x8d\x3d\xfd\x72\
-\xc3\x7f\x7a\x46\x0e\x5d\x4e\x25\xa3\x0c\x38\x3c\xad\x22\xfc\xdc\
-\x66\x37\x1a\x87\x57\x17\x3e\x45\x66\x4f\xf3\xe8\xaa\xe1\x9b\x39\
-\x00\x72\x65\x45\xbf\x97\xfb\x4f\xb7\xb6\xaf\x7c\xc5\x14\xed\x98\
-\x42\xfd\x06\x2d\xb1\xa1\x95\x2b\x42\x97\xb9\x28\x7a\x9c\x03\x2c\
-\xff\xa6\x29\x61\xb4\x87\x48\x7c\x5a\x17\x6d\xd3\x1f\xa7\x77\x31\
-\xc3\x30\x68\xd2\xc5\x01\xe7\xfc\x7e\x0b\xb7\xef\x78\x87\x5e\xe4\
-\x91\x20\xc9\xc2\x85\xb7\x43\xa6\x8e\xdf\xfa\x76\xae\x31\x93\xe2\
-\xae\x0a\xde\xcf\x0c\x99\xd0\xb7\x76\x8d\x53\x82\x86\x98\x15\x65\
-\xcc\xee\x8d\xa1\x13\xba\xa2\x8b\xa9\x0e\xca\x71\x39\x42\xf1\x49\
-\x61\xa3\xbe\xe5\xa4\x2c\xba\x22\xa8\xa9\x53\x79\xf8\xc3\x32\x99\
-\x67\xaa\xa7\x75\x5f\x54\x1b\xfe\x7e\xc3\xb7\x70\xac\x65\x98\xf2\
-\x6e\xad\xbf\xab\x0a\xa0\xa1\x2b\x0a\xff\x43\x6d\x6d\xf6\x7d\x07\
-\x09\xb8\xe2\xd2\xb6\x58\xa2\xe7\xf2\xab\x16\xb8\x7e\xcd\xa6\x0c\
-\x13\xc4\x7d\xdc\xa4\x99\xc7\x25\x4d\xbf\xbe\xa4\xd2\x77\x0e\xdd\
-\xdd\xf9\x42\x4f\xcf\x52\xf2\xff\xfb\xf3\xe9\xb1\xa7\xb3\xbb\x98\
-\xf2\x7e\x33\x86\xe2\x0d\x0d\xc1\xff\xb8\x2c\x84\x67\xb5\x82\x24\
-\xb4\x04\x61\x72\x37\xf5\x22\x9f\x7e\xad\x30\x6c\x5c\x17\x5c\x67\
-\x6b\x8c\x3a\x2a\xa2\x7d\xf7\xf2\x09\x07\xe8\x96\x2f\x5f\xb8\xbe\
-\x21\xed\x82\xed\xb0\x1d\xb0\x97\x1f\xf7\x8d\xec\x61\xbd\x7c\x71\
-\xe0\xc5\x70\xc4\x30\x09\xce\xb7\x0e\x03\xef\x76\x8f\x21\xaa\x63\
-\x2e\xc4\x5b\x0b\xe0\x9c\x1a\x4f\x71\x0b\x04\x48\x86\x6f\xd4\x15\
-\xfe\x31\x0b\xe6\xa7\x5e\xed\x0b\x20\x2f\x39\xe5\xeb\xbb\x4d\x0a\
-\x25\x2f\x3b\xf1\x81\xc8\xa2\x0f\x54\x49\x49\x03\x1d\x1d\xe7\x89\
-\x0e\x3b\x16\x31\x7e\x4b\x1e\x11\xe0\x03\x01\x97\xe9\x02\xcf\x93\
-\x54\x2a\x2f\x2a\x82\x7f\x81\x19\x64\x10\x11\x90\xf2\xbc\xb3\xb2\
-\xc0\xdb\xde\x10\x77\xd9\x18\x62\xb7\xa6\x1a\x6c\x30\xe1\xe2\x93\
-\x37\x7a\x8b\x9b\xa1\x7b\xc5\xa7\x68\xe6\x92\xc6\x0f\x82\x61\x3d\
-\x34\x3a\x5c\x1f\x99\xc3\x46\x69\x70\xc7\x2c\x19\x42\x3a\x7c\x24\
-\x61\x23\x3c\x91\x20\xae\xf3\x47\x38\xa6\x10\x4c\x57\x0c\xab\xfc\
-\x01\x3c\x36\xfc\x76\xfd\x01\x15\x54\x2a\x4e\xf7\xa8\xf7\x6f\x59\
-\xf2\xfd\x43\x67\x05\x93\x57\x6b\xf1\x81\x48\x6f\x15\x15\xeb\xa3\
-\xb5\xf5\x2f\x38\x79\xf2\x3e\x91\x04\x63\x2d\x5a\xbd\xa4\x02\x50\
-\x16\x18\x42\xc4\x3e\x2e\xe4\x78\x01\x14\xde\x56\x05\x9f\x92\xb6\
-\x90\xcf\x8a\x40\x49\xeb\xa8\x88\x17\x2c\x35\x70\x54\xd3\x7a\xa8\
-\xc0\x65\x10\x28\xc6\xf5\x10\x9c\x1b\x21\xac\xd0\x47\x38\x65\x86\
-\x10\xd5\xb9\x72\xd0\xd5\x11\x63\x95\x09\x07\x39\xf1\xec\x5e\x01\
-\xea\xab\x3e\x09\xa0\xdd\xa6\xe2\xee\xef\x2f\x0d\xfb\x3b\x4f\x0b\
-\xa7\x6d\x4b\xda\xc1\x40\x38\xa4\xac\xac\x2e\xb0\xb5\x1d\x8b\xe3\
-\x46\xaf\xc3\x99\x46\x20\x60\x25\x98\xdb\x12\x52\x76\xc9\xc3\x53\
-\x22\x01\x12\xde\x05\x36\x02\x5f\x41\x7b\x28\x95\x14\x81\x12\x66\
-\x29\x83\x1c\x5f\xab\x2f\x87\x5d\x95\x50\xf3\x44\x38\xc0\xa4\x49\
-\x6e\x16\xa1\xb9\x04\x7c\x69\xf8\x26\x7d\xc8\x23\x8b\x2b\x0a\xff\
-\x92\x34\xe8\x1f\xbc\x2e\x80\xee\xa4\x4d\x78\x51\xbf\xbe\x26\xda\
-\xea\xaa\x21\x2b\x00\xe5\x57\x63\x28\xd8\xa2\x0a\x21\x62\x09\xf0\
-\x20\x03\x2f\xe2\x9a\x43\x50\x15\x09\x84\x23\x16\xeb\x46\x7c\x53\
-\xf8\xbe\x46\xa1\x24\xf0\x82\xaa\xbb\x85\xe8\xcc\x81\x10\x2a\x80\
-\x5a\x93\x8a\x31\xfa\xa6\xd2\xa0\xff\x1f\x16\x86\x88\xc7\x0f\xe8\
-\xa8\xa0\xd0\x88\x3c\x77\xa7\xb7\x00\x49\x11\x84\xbf\x37\x01\x1f\
-\x56\x02\xca\x51\x19\x88\x4d\x35\x82\x47\xac\x04\x59\xe6\x70\x53\
-\x25\xac\xd3\xe3\xaf\x0a\xff\x7a\x13\x5f\x12\x76\x59\x75\x7b\x05\
-\xe9\x8c\x86\xf7\x86\x9d\x2a\x8a\xfe\x79\xd2\x90\xff\x9f\x57\x06\
-\x89\x77\xf2\x7e\xaa\x2c\x43\x9a\x6d\xba\x95\x24\xc0\x45\xfa\x10\
-\xe8\xc1\x81\x8f\x92\x22\x9c\x51\x80\xa0\x1c\x13\x48\x79\xd7\x09\
-\x1e\xdc\xb2\x51\xe3\x91\x80\x05\x35\x06\x4f\x6b\xfa\x67\x69\x07\
-\x50\x0d\x1b\x45\x11\x9a\x0e\x06\x94\xab\x27\x0a\xff\x61\x5d\x3f\
-\xd2\xad\xce\x2e\x0d\x13\x2f\x41\xf2\xa4\x07\x2b\x5a\x69\x00\xce\
-\x69\xf9\x49\x02\x52\x2f\x48\xd8\x23\x07\x69\x92\x12\x10\xf8\x57\
-\x35\xe0\x46\x96\x2e\xf8\x5a\x9f\x32\xf3\xad\x36\xfc\x47\x1d\x3f\
-\xc0\x11\xb9\xd0\xcf\x85\x2f\x6a\x05\x94\xf7\xf8\xe5\x7c\xef\xf1\
-\x08\xa9\x00\xff\x99\x08\x74\xd3\x24\x7e\x23\x05\xc0\xf1\xfa\x95\
-\x4a\x83\x77\x7f\x2a\x41\x4c\x25\x09\x18\xc8\x88\xea\x06\x05\xef\
-\xea\xcb\xa4\x71\xe2\xac\xdf\x56\x0a\x3f\xca\x32\x9d\xd4\xf4\x13\
-\x6a\x08\xfd\x03\xec\x81\x48\x98\x49\x9e\xf7\x83\xc8\x9f\xcb\x2b\
-\x7e\xc3\xa5\xe1\xd6\x92\xc5\xa1\xe2\x39\x05\x19\x32\xa4\x4d\xdf\
-\xbb\x71\x25\x09\xf8\x6b\x35\xc1\x4f\x52\x82\xc0\x4e\x90\x5e\xa0\
-\x0a\xbe\x1b\xa6\xea\x04\x54\x84\x1f\x6c\xf6\x04\xbc\x98\xcc\x4f\
-\xfb\x01\x42\x2e\xec\x85\x50\xd8\x00\x3c\x98\x0f\x8f\xc1\x0d\x4a\
-\x09\x08\x2e\x04\x25\x51\xf8\x07\xa4\xc1\xd6\xb2\xd5\xc1\xe2\xad\
-\xd5\x68\x37\x30\x1a\x2a\x03\x4e\x93\xa8\x20\x2e\x6b\x06\xbe\xe4\
-\x59\xc1\xa7\x02\xdc\x34\x82\xe0\xf8\xf6\x90\x4c\xbe\x43\xa8\x11\
-\xdc\x31\x06\x1e\xb4\x08\x21\x81\xbf\x82\xdd\x10\x04\xeb\x48\xe0\
-\x73\x21\x81\x84\x2d\x10\x05\x2e\x89\x2b\x41\x5b\x14\xfe\x13\x69\
-\x87\x4f\x2d\x5d\x1e\x2e\x9e\xd4\xb0\x86\xce\x17\x50\x22\x15\xc4\
-\x21\x4d\x3f\x49\x30\xdf\x08\xa2\xf7\x72\xe1\xdd\x19\x75\xf0\x0d\
-\xef\x0e\xc8\x97\x85\xe8\x64\x1d\xb9\x64\x99\x39\x90\xf4\x8f\xb0\
-\xab\xa3\xfc\xb9\x5f\x44\x68\x2b\x0d\xb5\x96\xef\x0f\x20\xee\x52\
-\x7e\x45\x4b\x83\xf6\xea\x80\xb3\xc5\x15\x44\x52\x51\xcc\x70\xd7\
-\x00\x6f\x2a\x40\x6a\x0b\x08\xa4\x6f\xcd\xab\x35\xb9\xeb\xbf\x14\
-\xbe\x5d\x45\x93\x6f\xbc\x34\xd0\x3a\xb2\x41\x84\x78\x90\x69\x0a\
-\xad\x1b\x68\xca\x03\x8e\x13\x57\x10\x7f\x6d\x09\x85\xa1\xdd\x41\
-\x10\x61\x0b\x7c\x64\x20\x53\x08\xf0\xa1\xd9\x28\xc8\xa8\x31\xfc\
-\xe1\x84\xf2\x61\xde\xdd\xd2\x30\xeb\xe0\x0e\x21\xe2\xd9\xbe\x77\
-\x64\x18\xc0\x9e\xe2\x0a\xe2\xc5\x4e\x90\x49\x4b\x81\x9c\x86\xe0\
-\x43\xdf\x5e\x52\x7d\x08\xac\x36\xfc\x09\x84\xf2\x19\x3e\xb7\xfe\
-\x57\x9b\x2c\x4a\x05\xf8\x7e\x22\xd0\xd2\xa0\x4c\x9f\xd4\xe2\x97\
-\x1b\x83\x80\x0a\x10\xdb\x09\x32\xc8\xbf\x94\xd2\xb7\x38\xa2\x37\
-\x44\xfc\x43\x00\x3d\x51\xf8\x8f\x7e\xc4\x34\x74\xa9\x00\xff\x3b\
-\x09\x70\x38\xa9\xcd\x53\x01\x28\x45\x8a\x10\x44\xdf\x62\x31\x07\
-\x5e\xc8\x4d\x81\x92\x8a\xf0\x3b\x8a\xc2\x7f\xf7\x9f\xce\x32\x92\
-\x52\x4b\xf7\x08\x22\xaf\xdf\xe5\x38\xa4\x42\x68\x00\x48\xea\x02\
-\x18\x6c\x0c\xa9\xa2\xb7\x48\xc7\x0e\x0c\x21\x40\x14\x7e\xbf\x8a\
-\x99\xbd\x4e\xd2\x00\x7f\xc2\x5d\xc2\xd8\x9d\x41\xcd\xea\x03\x9e\
-\xef\x00\xf8\x9c\x53\xfe\x18\x20\x6d\x3c\xa1\x5e\x6f\x12\xbc\x02\
-\x54\x5a\xdd\x2a\xe5\x27\xdc\x26\x4e\xbc\x1a\x26\x9d\x96\x06\x6e\
-\x24\xf0\x25\xe4\x6d\xf6\xa3\x4b\xa4\x98\x8a\x4a\x9f\x82\x34\xbc\
-\x9f\x7c\x9f\x40\xf1\x80\x12\x3d\x7c\x21\x5d\xbc\xb8\x81\x72\x50\
-\x5a\xe3\xff\x17\x9f\x18\x22\x45\x2a\x80\x94\xef\xcc\xff\x01\x19\
-\x47\x8e\x78\xd3\x1b\x66\xf0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
-\x42\x60\x82\
-\x00\x00\x05\x24\
+\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\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\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\x06\xec\x00\x00\x06\xec\
-\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\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\x13\x74\x45\
-\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\
-\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x82\x49\x44\x41\
-\x54\x48\x89\x8d\x96\x6d\x88\x54\x55\x18\xc7\x7f\xe7\xbe\xcd\xdc\
-\xb9\xb3\xb3\x33\xfb\x1a\xeb\xbe\xe9\x6e\xad\x2f\x69\xea\x4a\x68\
-\x8a\x22\x24\x94\x58\x7e\xaa\x48\x23\x30\x41\xa1\xec\x5b\x51\x41\
-\xf8\x31\xa3\x3e\x44\x94\x44\x92\x09\xa5\x46\x54\x84\x21\x59\x44\
-\x18\x96\x19\xe8\xb2\x5a\x9b\xba\xea\xa4\xfb\x66\xee\xce\xce\xec\
-\xec\xcc\xdc\xbd\xf3\x76\xef\xe9\x43\xb3\x63\xe6\xbe\x3d\xf0\x7c\
-\xfb\x9f\xff\xef\x3c\xcf\x3d\xf7\x39\x47\x48\x29\x99\x2d\x36\xbf\
-\x27\x7c\x6a\x40\x7f\xc0\x6f\xaa\x8f\x02\x64\x1d\xf7\x84\x3b\x51\
-\xb8\xf0\xed\x8b\x32\x37\xdb\x5a\x31\x1d\x60\xf7\x01\xa1\x67\x2a\
-\x42\x6f\x37\xd7\x76\xac\xaf\x0a\xd6\xd5\x07\x2c\x7f\xb5\x69\x05\
-\x7c\x52\xba\x38\xb6\x93\xb3\xed\x6c\x62\xdc\x8e\xdd\xea\x8b\x5d\
-\x39\x15\x4c\xa7\x5e\xfe\x70\x97\x2c\xcc\x19\xb0\xf5\xa0\xb1\xa2\
-\xb9\x7e\xfe\xa1\x65\x1d\xab\x96\x7a\x5a\x56\xc9\xbb\x0e\x92\x3b\
-\x75\x02\x81\xa1\x9a\x50\x50\xbd\x9e\x2b\xe7\xff\xe8\x1f\xbe\xb1\
-\xe3\xd8\xce\x7c\xf7\xac\x80\x6d\x9f\x87\xf7\x2d\x69\x59\xf9\x5c\
-\x5d\x7d\x4d\x9d\xe3\xa6\x67\xeb\x00\x00\x01\x35\x44\x7c\x78\x74\
-\xe4\x42\x5f\xd7\xc7\x47\x9f\x4a\xbe\x36\x2d\xe0\x89\xc3\xe6\xb3\
-\x6b\x96\xae\xdf\x6f\x04\xf5\xa0\x27\xdd\x39\x99\x87\xb4\x7a\xaa\
-\xb4\x05\xa4\x8a\x43\x8c\x24\xaf\x65\xce\xf4\xfc\xfa\xc2\x17\xcf\
-\x38\x9f\xdc\x05\x78\xfc\x80\xa8\x59\xdc\xb6\xfc\xb7\xc6\xd6\x79\
-\x6d\x73\x33\x17\x6c\xa8\xdc\x43\x83\xb1\x0c\x9f\x08\x72\xc9\xf9\
-\x9e\x73\x99\x4f\x19\xb8\xd1\x17\xbd\x18\xed\x59\xfd\xcd\x2e\x39\
-\x0a\xa0\x4c\xca\xab\xab\xea\x8e\x34\x35\x37\xcc\xd1\x1c\x56\x5a\
-\x4f\xd2\xea\x5b\x8d\x5f\x54\x10\x2b\x46\x39\x97\x39\x8c\x2b\x5d\
-\x5a\x9a\x5b\xdb\xaa\xab\x6a\x8f\x4c\xea\x14\xf8\xf7\xa3\xde\xd7\
-\xd2\xb1\xd6\x15\x73\x33\xaf\xd1\xdb\xb9\xd7\xdc\x88\x82\x4a\xda\
-\x1d\xe1\x97\xd4\x07\xb8\xb2\x08\x40\x51\xb8\xb4\x37\xcf\x5f\xbb\
-\xf5\xa0\xb1\xa2\x0c\xf0\xfb\xd4\x2d\x56\xd0\xb4\x40\x20\xc4\x54\
-\xa9\x94\x53\x53\xfc\xac\x0b\xed\x22\xa0\x86\xc9\x4b\x87\x4b\xce\
-\x09\xd2\xee\x2d\x14\xa1\xa2\x08\x0d\x21\x54\x02\x96\xdf\xf2\xfb\
-\xd4\x2d\x00\x1a\x80\x65\x55\x76\xaa\x86\x86\xbc\xdd\xb1\x52\x97\
-\xc5\x5d\xbb\x5f\x57\xb1\x9b\x88\xd6\x02\xc0\xad\x42\x0f\x97\xb3\
-\x3f\xa2\x08\xed\x0e\x8d\x66\xe8\x58\x66\xa8\xb3\x0c\x08\x9b\x91\
-\x26\x4f\x4a\x54\x45\x9d\xd1\x7c\x81\xff\x21\x1a\x7d\xcb\xf1\x3c\
-\x8f\x91\x42\x2f\xc7\xe3\x7b\x31\xd5\x10\x86\x12\xb8\x43\x57\x14\
-\x0a\xe1\x40\xa8\xa9\x0c\x30\x0d\xb3\x5a\x11\x3a\x0a\x0a\x4c\x61\
-\x0c\x10\x50\x23\x2c\x31\x1f\x43\xb8\x3a\x49\xf7\x26\xc7\xe3\x7b\
-\x89\xe5\xa3\x00\x58\x6a\x15\x11\xa3\x11\x53\x0d\x23\x10\xe4\xbd\
-\x1c\xa6\xe1\xab\x2e\x03\x9c\xfc\x44\x5c\x57\x8c\x96\xff\x9a\x77\
-\x98\x9b\x08\x28\x61\xba\xed\x2f\xf1\x64\x91\x4e\x73\x1b\x16\xb5\
-\x14\x65\x8e\x0b\xf6\x57\x0c\xe5\xcf\x97\xb5\xb6\x9b\xc0\x76\x12\
-\xf8\x14\x8b\xb0\xde\x48\xde\x4b\xe1\xe4\xb3\xf1\x32\x20\x69\x8f\
-\x0d\x08\xe9\xad\x14\xc2\x00\x60\x51\xe0\x11\x96\x04\x36\x23\x50\
-\x18\x2b\x0c\x52\x70\x73\xdc\xa3\x2d\x06\xa0\x3f\x77\x96\x53\xa9\
-\xfd\x53\x56\x99\xf3\x6c\x86\x73\xbd\x54\xa9\xb5\x24\x27\xc6\x07\
-\xca\x00\xdb\x49\x75\x25\x9d\xa1\xad\xe8\x3a\xa6\x5a\x89\x81\x85\
-\xf0\x34\x3c\xcf\xa3\xcd\xd8\x80\x2e\x4c\x14\xa1\x13\x2b\x5c\xe5\
-\x58\xe2\x55\x60\xe6\x09\xac\xba\x3a\xb6\x93\xe9\x2a\x03\xb2\x39\
-\xf7\x78\x2e\x53\x7c\x65\x3c\xd0\x67\x49\x3c\xbc\xa2\xa4\xb5\x76\
-\x0d\x9a\xf0\x53\x55\x3a\x31\xb6\x1b\xe7\x64\xea\x1d\x26\xbc\xc4\
-\x8c\xe6\x02\x85\x82\x2d\xed\x6c\xce\x3d\x0e\xa5\xff\xe0\xd8\xce\
-\x7c\x77\x74\xa0\xff\x74\x8d\xd2\x00\xc0\x60\xbe\x9b\x44\xb1\xbf\
-\xbc\xc8\x93\x2e\x97\x9c\xef\xb8\xea\xfc\x34\xa3\x39\x40\x8d\x52\
-\x47\x74\x70\xf0\xf4\xe4\x64\x2d\x1f\xfc\x78\x22\xb6\x3d\x31\xe4\
-\x44\x4d\xc5\xc2\xc3\xe5\x77\xfb\x6b\x3c\x59\xa4\x20\x1d\x06\xf2\
-\xe7\xf8\x21\xf9\xe6\xac\xe6\xa6\x12\x20\x31\xe4\x44\xe3\x89\xf8\
-\xf6\x72\x45\xff\x9f\xa6\x9d\x0b\xef\x7f\x3f\xe9\xff\xbb\xc2\x95\
-\x1e\x0b\xcd\x87\xc9\xca\x34\xd7\xb3\x67\x98\xad\xef\x9a\xd0\x08\
-\x3b\xb5\xe9\xae\xde\xde\x3d\x53\x4e\xd3\xc9\xd8\xf6\x59\x78\x5f\
-\xc7\xbc\x96\x1d\x6a\x4d\xa1\x3e\xe9\xc6\x67\xdd\x35\x40\x58\x8d\
-\xe0\x8e\x6a\xc3\xbd\x43\xfd\x87\x8e\x3e\x9d\x9a\xfe\x3e\x98\x8c\
-\x4d\x6f\x19\xab\xdb\xda\xeb\x3f\x5a\xd0\x5e\xb7\x28\xad\x24\x94\
-\xac\x9c\xfa\x46\xf3\x0b\x3f\x15\x6e\xd8\xfb\xeb\xda\xc8\xe5\x8b\
-\x5d\xb1\xe7\x7f\xde\x57\x3c\x2d\x65\x69\xea\x4d\x05\x10\x42\xa8\
-\x40\x35\x10\x36\x2b\xa8\xdd\xf8\x7a\xf0\xa5\x96\xe6\xea\x65\xa1\
-\x50\x20\x12\x08\x2b\x41\xa3\x42\xd1\x01\x72\x29\xaf\x38\x91\x2c\
-\x66\xd2\xa9\x6c\xf2\xfa\xf5\xd8\x9f\x27\xdf\x98\x78\x37\x67\x33\
-\x0a\x8c\x01\x49\x29\x65\x72\xda\x0a\x84\x10\x16\x10\x01\x2a\x4b\
-\x59\x61\x04\x89\x34\x3c\xa8\x2c\x6c\x5a\xa5\xad\x90\x12\x65\xe0\
-\x6c\xf1\xfc\xcd\xb3\x5e\xb4\x60\x33\x06\x64\x80\x14\x30\x5e\xca\
-\x84\x94\xb7\x1f\x00\xd3\xbe\x2a\x4a\x30\x1d\x30\x00\x5f\x29\x75\
-\x40\x05\x8a\x40\x0e\xc8\x03\x59\xa0\x20\xe5\xd4\x37\xd5\x3f\x13\
-\x05\x02\x8c\xec\xcf\x7e\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
-\x42\x60\x82\
-\x00\x00\x05\x64\
+\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\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\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\x04\xe1\x49\x44\
-\x41\x54\x48\x89\xb5\x95\x4b\x6c\x54\x55\x18\xc7\x7f\xe7\x9c\x3b\
-\xd3\x99\x32\xb5\xf4\x41\x1f\x38\x02\xa5\x8d\x4c\x54\xb0\x25\x48\
-\xa2\x12\x62\x14\x17\x46\x12\x64\x81\x26\xb0\x31\x18\x12\x36\x04\
-\xd2\x45\xe9\xca\x84\x5d\x29\x1a\x43\x80\x6e\x0c\x2b\xdc\x88\x09\
-\xa0\x21\x04\x35\x18\xa2\x26\x3e\xa3\x62\xa8\x4f\x7c\x61\x95\x81\
-\xb6\x4c\xcb\xbc\x67\xee\x39\x9f\x8b\xdb\xde\x4e\x2b\x10\x37\xde\
-\xe4\x4b\xee\xf3\xf7\xff\xfe\xdf\xf9\xee\x77\x94\x88\xf0\x7f\x1e\
-\xde\xdd\x1e\x9e\x53\xaa\x23\x6a\xcc\x33\xf1\x58\xec\xc9\xc6\xae\
-\x15\x2b\xeb\x97\x76\x2e\xb5\xc5\x62\x29\xfb\xd7\x5f\x63\xb9\x6b\
-\xd7\x7f\x2c\x55\x2a\xe7\x2b\xd6\x7e\xb0\x59\xa4\x70\x27\x86\xba\
-\xad\x03\xa5\xd4\x87\xb1\xd8\xcb\xc9\xc7\x1e\xdd\xdd\xd9\xde\xd6\
-\x11\xab\x8b\xa3\x4a\x45\xc8\xe5\xc1\x18\x48\x2c\xc2\x79\x86\x5c\
-\x21\xef\x7e\x1f\xbd\x7c\x65\xe2\xa7\x5f\xf7\x3c\x59\xad\xbe\xf7\
-\x9f\x04\xce\x2b\xb5\xa2\x75\x69\xe7\x1b\xa9\x0d\x8f\xaf\x4f\x54\
-\x6d\x84\x52\x29\x7c\x26\xc0\xec\xfb\x32\x13\x34\x24\xb8\x91\x9f\
-\x9e\xfa\xe5\xe3\x4f\xdf\xb6\xb7\xb2\xbb\x9f\x10\x29\xd5\xf2\xe6\
-\x09\xbc\xa7\x54\x6a\xc5\xda\xde\xf7\x7b\xba\xbb\x93\x3a\x57\x98\
-\x83\x2c\x80\x2e\x14\x11\xcf\xc3\xc6\x23\xf2\xdd\xa7\x9f\x7d\x91\
-\x1f\xbb\xf6\xf8\x13\x22\xfe\x2c\x53\xcf\x9e\x1c\x50\x4a\xb7\x2e\
-\x5f\x76\xa2\x67\xf9\xb2\x24\xd9\x3c\x4e\x24\x0c\x0b\xd8\x9a\x6b\
-\x07\xc1\x3d\xc0\x01\xce\xf7\x21\x5b\x54\x3d\x0f\xaf\x5e\xa7\xeb\
-\xeb\x5f\xab\x75\x10\x0a\x6c\x8a\xc7\x87\xee\x7f\xe8\x81\x3e\x29\
-\x56\x02\x80\x08\x95\xed\xdb\xa9\x6e\xd9\x32\x27\x52\x03\xb5\x9e\
-\x87\x1d\x18\xc0\xef\xed\x0d\xc5\x8d\x55\x3a\xd9\xf7\xe0\x0b\xef\
-\x46\xa3\xeb\x67\xb9\x1e\xc0\x05\xa5\xee\x5f\xf9\xd8\xfa\x17\xeb\
-\x7c\x67\x1c\x0a\x01\xec\x8e\x1d\xf8\xcf\x3d\x17\x64\xa1\x14\xfa\
-\xf4\xe9\xb0\x3c\x2e\x12\x81\xfd\xfb\xa1\xb7\x17\xfa\xfa\x70\x43\
-\x43\xf0\xf5\xd7\x88\x15\x9a\x1b\x9b\x96\x34\x25\xdb\x5f\x47\xa9\
-\x5e\x44\x44\x03\x78\x9e\xf7\x52\xc7\xe2\xa6\x25\x0e\x15\x64\xe9\
-\x79\xd8\x9e\x9e\xd0\xa6\xdb\xbe\x1d\x7f\xeb\xd6\x20\xd3\x68\x14\
-\x06\x07\x51\x7d\x7d\x28\xa5\x50\x91\x08\x74\x77\x63\x95\xc2\x29\
-\x85\xad\x38\x5a\xdb\x5a\x97\x9d\x85\x9e\xd0\xc1\xa2\xd6\x96\x07\
-\x11\x70\x5a\x07\x59\x3a\x07\x43\x43\xa8\xc1\x41\x58\xbd\x3a\x50\
-\xd9\xb1\x03\x17\x89\xa0\x52\x29\xd4\x9a\x35\xa1\xb8\x3d\x79\x12\
-\xff\xcc\x19\xc4\x18\x10\x41\xb4\xa6\x2e\x16\x5b\x1c\xd1\xfa\x29\
-\xe0\x67\x0d\x10\x6b\x6d\xba\xcf\x39\xc1\x69\x3d\x17\xd6\xe2\x86\
-\x87\x91\xcb\x97\xe7\x16\xec\xf9\xe7\x43\xb8\x88\xe0\xbf\xf9\x26\
-\xd5\x53\xa7\x82\xc4\xb4\xc6\x79\x1e\x62\x0c\x26\x12\xa5\x2e\x11\
-\xdf\x00\xa0\xdf\x52\x2a\x51\xb7\x28\xde\xee\xbc\xc8\x7c\x01\xad\
-\x71\xce\xe1\xbf\xf2\x0a\x32\x3a\x3a\x57\x2e\xe7\xf0\x7d\x9f\xe2\
-\xa1\x43\x64\x07\x06\x28\x5f\xb9\x82\x2d\x95\x70\xc6\x04\xdf\x18\
-\x83\xad\x5a\xe2\x4b\x9a\x97\x87\x25\xc2\x98\x60\xe1\x6a\x7b\x7b\
-\x06\x28\xc6\x20\x4a\x81\x13\x9c\xb8\xf0\x1f\x70\x80\xb5\x16\x3f\
-\x9d\x46\xd2\x69\x74\x4b\x0b\x5e\x32\x89\x6e\x6e\xc6\x96\xcb\x88\
-\xb8\xa0\x8b\xb6\x89\xe4\xbe\x79\x68\xd5\x75\xb9\xd7\xb4\x8b\x95\
-\x00\xae\x54\x00\x8f\x44\x88\xec\xdb\x07\xa9\x14\xd6\xd9\xda\xf6\
-\x26\xd6\xdf\x8f\x03\x0a\x87\x0f\x07\x5d\x37\x31\x41\x65\x7c\x1c\
-\xe2\x71\xbc\xce\x26\xf2\x13\x53\x7f\x84\xff\x41\x71\x32\xf3\xa7\
-\x3f\x39\x81\xd3\x26\xb4\xea\x97\xca\xe8\x5d\xbb\x90\x55\xab\x70\
-\x2e\xc8\xa6\xf0\xea\xab\x54\x3f\xfa\x28\x14\xa9\xef\xef\x27\xb6\
-\x77\x2f\x56\x24\x08\xc0\x2f\x14\xa8\x7a\x9a\x72\xae\xf8\x71\x58\
-\xa2\xc2\x44\x66\xb4\x9c\xcd\x3c\xeb\x46\x7f\x40\x25\x12\x38\xdf\
-\x27\x3e\x32\x82\x5e\xbb\x36\x84\xe5\x87\x87\x29\x1c\x3b\x86\x44\
-\xa3\x34\x1e\x3f\x4e\xdd\xc6\x8d\x00\x24\xfa\xfb\x71\xbe\x4f\xf6\
-\xc8\x91\xb0\xb4\x3e\x4c\x39\xe7\x2e\x84\x0e\xf0\xfd\xe3\x19\xe7\
-\xc6\x2d\x8e\xca\xf8\x38\xd5\x4c\x06\xff\xd2\xa5\x10\x9e\x3b\x78\
-\x90\xdc\xd1\xa3\x41\x96\xe5\x32\x93\x3b\x77\x52\xba\x78\x31\x28\
-\x63\xb9\x4c\xe9\xdb\x6f\xf1\x67\x5c\xe8\xf6\x26\xa6\x27\x32\x57\
-\x37\xc3\x15\xa8\x19\x76\x17\xea\xeb\x87\xbb\xfa\x52\xfd\xf6\xf2\
-\x2f\x46\x24\x58\x8b\xc4\xe0\x20\xf6\xd6\x2d\x72\x23\x23\x41\x8f\
-\xd7\x36\x41\x34\x4a\xeb\xc8\x08\xd9\x13\x27\x28\x5c\xbc\x18\x0c\
-\xc4\xa8\x87\xac\xec\x18\xff\xfb\xab\x9f\x36\x6f\x16\xf9\x7c\x9e\
-\xc0\x01\xa5\xf4\xa6\x64\xfb\x67\x1d\x0d\x0d\xeb\x2a\x63\xd7\x91\
-\x05\xc0\xd9\xde\x9f\x27\x52\x73\x0d\x50\x97\xba\xcf\xa5\xbf\xff\
-\x7d\xe4\xe9\x42\x69\xcf\xac\xfb\x79\xe3\xfa\xac\x52\xa9\xb6\x54\
-\xd7\xfb\x8d\xc6\x24\x2b\x7f\xa4\xff\x05\x58\x08\x0d\x47\xb7\x67\
-\x88\x76\x77\xca\x74\xfa\xe6\x17\x92\x9e\x9c\x37\xae\x6f\xbb\xe1\
-\xc4\xda\x9a\xdf\x58\xb2\x62\xe9\x23\xfe\x6f\xd7\xa2\x36\x5f\x9c\
-\x2f\xb0\x40\xc4\x6b\x6b\x44\x9a\x13\x99\xc9\x1f\xc7\xde\x89\x15\
-\x4a\x77\xdf\x70\x00\x94\x52\x0d\x31\x68\x39\x12\x31\x03\xa9\x55\
-\xcb\xb7\x35\x18\xd3\xaa\x7c\x8b\x9d\xce\x53\x9d\xce\x83\xd1\x98\
-\xc5\x09\x74\x43\x0c\xab\x95\x4c\x66\x72\x63\x17\xae\x5e\x3f\x78\
-\x08\xde\x05\xa6\x80\x29\xb9\x93\x03\xa5\x54\x04\x68\x01\x16\x03\
-\x4d\xbd\xd0\xb5\x49\xeb\x8d\x5d\x9e\xe9\x6b\x69\x69\x68\x6b\x6c\
-\x5c\xd4\x58\xf5\xad\xbd\x99\xc9\x4d\xdf\x98\xce\x5f\x1b\xf5\xed\
-\x97\xe7\xe0\x93\x71\x48\xcf\xc2\x81\x8c\x88\x64\xef\xe8\x60\x46\
-\xa8\x1e\xb8\x67\x26\xea\x81\x38\x50\x07\x18\x40\x11\xec\x3b\x15\
-\xa0\x0c\xe4\x81\x1c\x70\x0b\xc8\xca\xec\x8c\x98\x39\xfe\x01\x76\
-\x95\xba\xf1\x06\x3a\xff\x81\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
-\x42\x60\x82\
+\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 = "\
@@ -1078,47 +755,29 @@ qt_resource_name = "\
\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\x13\
-\x09\xd2\x6c\x67\
-\x00\x45\
-\x00\x6d\x00\x62\x00\x6c\x00\x65\x00\x6d\x00\x2d\x00\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x69\x00\x6f\x00\x6e\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\
-\x00\x14\
-\x00\xe9\x23\x87\
-\x00\x6c\
-\x00\x65\x00\x61\x00\x70\x00\x2d\x00\x63\x00\x6f\x00\x6c\x00\x6f\x00\x72\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\x00\x2e\
-\x00\x70\x00\x6e\x00\x67\
-\x00\x11\
-\x06\x1a\x44\xa7\
-\x00\x44\
-\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x61\x00\x63\x00\x63\x00\x65\x00\x70\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\
-\
-\x00\x10\
-\x0f\xc3\x90\x67\
-\x00\x44\
-\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\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\x07\x00\x00\x00\x02\
-\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x03\
-\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x89\
+\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\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x36\x7b\
-\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x05\x99\
-\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x37\
-\x00\x00\x01\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x3b\xa3\
+\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():
diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py
deleted file mode 100644
index ca4f6cc3..00000000
--- a/src/leap/gui/progress.py
+++ /dev/null
@@ -1,488 +0,0 @@
-"""
-classes used in progress pages
-from first run wizard
-"""
-try:
- from collections import OrderedDict
-except ImportError: # pragma: no cover
- # We must be in 2.6
- from leap.util.dicts import OrderedDict
-
-import logging
-
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-from leap.gui.threads import FunThread
-
-from leap.gui import mainwindow_rc
-
-ICON_CHECKMARK = ":/images/Dialog-accept.png"
-ICON_FAILED = ":/images/Dialog-error.png"
-ICON_WAITING = ":/images/Emblem-question.png"
-
-logger = logging.getLogger(__name__)
-
-
-class ImgWidget(QtGui.QWidget):
-
- # XXX move to widgets
-
- def __init__(self, parent=None, img=None):
- super(ImgWidget, self).__init__(parent)
- self.pic = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- painter = QtGui.QPainter(self)
- painter.drawPixmap(0, 0, self.pic)
-
-
-class ProgressStep(object):
- """
- Data model for sequential steps
- to be used in a progress page in
- connection wizard
- """
- NAME = 0
- DONE = 1
-
- def __init__(self, stepname, done, index=None):
- """
- @param step: the name of the step
- @type step: str
- @param done: whether is completed or not
- @type done: bool
- """
- self.index = int(index) if index else 0
- self.name = unicode(stepname)
- self.done = bool(done)
-
- @classmethod
- def columns(self):
- return ('name', 'done')
-
-
-class ProgressStepContainer(object):
- """
- a container for ProgressSteps objects
- access data in the internal dict
- """
-
- def __init__(self):
- self.dirty = False
- self.steps = {}
-
- def step(self, identity):
- return self.steps.get(identity, None)
-
- def addStep(self, step):
- self.steps[step.index] = step
-
- def removeStep(self, step):
- if step and self.steps.get(step.index, None):
- del self.steps[step.index]
- del step
- self.dirty = True
-
- def removeAllSteps(self):
- for item in iter(self):
- self.removeStep(item)
-
- @property
- def columns(self):
- return ProgressStep.columns()
-
- def __len__(self):
- return len(self.steps)
-
- def __iter__(self):
- for step in self.steps.values():
- yield step
-
-
-class StepsTableWidget(QtGui.QTableWidget):
- """
- initializes a TableWidget
- suitable for our display purposes, like removing
- header info and grid display
- """
-
- def __init__(self, parent=None):
- super(StepsTableWidget, self).__init__(parent=parent)
-
- # remove headers and all edit/select behavior
- self.horizontalHeader().hide()
- self.verticalHeader().hide()
- self.setEditTriggers(
- QtGui.QAbstractItemView.NoEditTriggers)
- self.setSelectionMode(
- QtGui.QAbstractItemView.NoSelection)
- width = self.width()
-
- # WTF? Here init width is 100...
- # but on populating is 456... :(
- #logger.debug('init table. width=%s' % width)
-
- # XXX do we need this initial?
- self.horizontalHeader().resizeSection(0, width * 0.7)
-
- # this disables the table grid.
- # we should add alignment to the ImgWidget (it's top-left now)
- self.setShowGrid(False)
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- #self.setStyleSheet("QTableView{outline: 0;}")
-
- # XXX change image for done to rc
-
- # Note about the "done" status painting:
- #
- # XXX currently we are setting the CellWidget
- # for the whole table on a per-row basis
- # (on add_status_line method on ValidationPage).
- # However, a more generic solution might be
- # to implement a custom Delegate that overwrites
- # the paint method (so it paints a checked tickmark if
- # done is True and some other thing if checking or false).
- # What we have now is quick and works because
- # I'm supposing that on first fail we will
- # go back to previous wizard page to signal the failure.
- # A more generic solution could be used for
- # some failing tests if they are not critical.
-
-
-class WithStepsMixIn(object):
- """
- This Class is a mixin that can be inherited
- by InlineValidation pages (which will display
- a progress steps widget in the same page as the form)
- or by Validation Pages (which will only display
- the progress steps in the page, below a progress bar widget)
- """
- STEPS_TIMER_MS = 100
-
- #
- # methods related to worker threads
- # launched for individual checks
- #
-
- def setupStepsProcessingQueue(self):
- """
- should be called from the init method
- of the derived classes
- """
- self.steps_queue = Queue.Queue()
- self.stepscheck_timer = QtCore.QTimer()
- self.stepscheck_timer.timeout.connect(self.processStepsQueue)
- self.stepscheck_timer.start(self.STEPS_TIMER_MS)
- # we need to keep a reference to child threads
- self.threads = []
-
- def do_checks(self):
- """
- main entry point for checks.
- it calls _do_checks in derived classes,
- and it expects it to be a generator
- yielding a tuple in the form (("message", progress_int), checkfunction)
- """
-
- # yo dawg, I heard you like checks
- # so I put a __do_checks in your do_checks
- # for calling others' _do_checks
-
- def __do_checks(fun=None, queue=None):
-
- for checkcase in fun(): # pragma: no cover
- checkmsg, checkfun = checkcase
-
- queue.put(checkmsg)
- if checkfun() is False:
- queue.put("failed")
- break
-
- t = FunThread(fun=partial(
- __do_checks,
- fun=self._do_checks,
- queue=self.steps_queue))
- if hasattr(self, 'on_checks_validation_ready'):
- t.finished.connect(self.on_checks_validation_ready)
- t.begin()
- self.threads.append(t)
-
- def processStepsQueue(self):
- """
- consume steps queue
- and pass messages
- to the ui updater functions
- """
- while self.steps_queue.qsize():
- try:
- status = self.steps_queue.get(0)
- if status == "failed":
- self.set_failed_icon()
- else:
- self.onStepStatusChanged(*status)
- except Queue.Empty: # pragma: no cover
- pass
-
- def fail(self, err=None):
- """
- return failed state
- and send error notification as
- a nice side effect. this function is called from
- the _do_checks check functions returned in the
- generator.
- """
- wizard = self.wizard()
- senderr = lambda err: wizard.set_validation_error(
- self.current_page, err)
- self.set_undone()
- if err:
- senderr(err)
- return False
-
- @QtCore.pyqtSlot()
- def launch_checks(self):
- self.do_checks()
-
- # (gui) presentation stuff begins #####################
-
- # slot
- #@QtCore.pyqtSlot(str, int)
- def onStepStatusChanged(self, status, progress=None):
- status = unicode(status)
- if status not in ("head_sentinel", "end_sentinel"):
- self.add_status_line(status)
- if status in ("end_sentinel"):
- #self.checks_finished = True
- self.set_checked_icon()
- if progress and hasattr(self, 'progress'):
- self.progress.setValue(progress)
- self.progress.update()
-
- def setupSteps(self):
- self.steps = ProgressStepContainer()
- # steps table widget
- if isinstance(self, QtCore.QObject):
- parent = self
- else:
- parent = None
- self.stepsTableWidget = StepsTableWidget(parent=parent)
- zeros = (0, 0, 0, 0)
- self.stepsTableWidget.setContentsMargins(*zeros)
- self.errors = OrderedDict()
-
- def set_error(self, name, error):
- self.errors[name] = error
-
- def pop_first_error(self):
- errkey, errval = list(reversed(self.errors.items())).pop()
- del self.errors[errkey]
- return errkey, errval
-
- def clean_errors(self):
- self.errors = OrderedDict()
-
- def clean_wizard_errors(self, pagename=None):
- if pagename is None: # pragma: no cover
- pagename = getattr(self, 'prev_page', None)
- if pagename is None: # pragma: no cover
- return
- #logger.debug('cleaning wizard errors for %s' % pagename)
- self.wizard().set_validation_error(pagename, None)
-
- def populateStepsTable(self):
- # from examples,
- # but I guess it's not needed to re-populate
- # the whole table.
- table = self.stepsTableWidget
- table.setRowCount(len(self.steps))
- columns = self.steps.columns
- table.setColumnCount(len(columns))
-
- for row, step in enumerate(self.steps):
- item = QtGui.QTableWidgetItem(step.name)
- item.setData(QtCore.Qt.UserRole,
- long(id(step)))
- table.setItem(row, columns.index('name'), item)
- table.setItem(row, columns.index('done'),
- QtGui.QTableWidgetItem(step.done))
- self.resizeTable()
- self.update()
-
- def clearTable(self):
- # ??? -- not sure what's the difference
- #self.stepsTableWidget.clear()
- self.stepsTableWidget.clearContents()
-
- def resizeTable(self):
- # resize first column to ~80%
- table = self.stepsTableWidget
- FIRST_COLUMN_PERCENT = 0.70
- width = table.width()
- #logger.debug('populate table. width=%s' % width)
- table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT)
-
- def set_item_icon(self, img=ICON_CHECKMARK, current=True):
- """
- mark the last item
- as done
- """
- # setting cell widget.
- # see note on StepsTableWidget about plans to
- # change this for a better solution.
- if not hasattr(self, 'steps'):
- return
- index = len(self.steps)
- table = self.stepsTableWidget
- _index = index - 1 if current else index - 2
- table.setCellWidget(
- _index,
- ProgressStep.DONE,
- ImgWidget(img=img))
- table.update()
-
- def set_failed_icon(self):
- self.set_item_icon(img=ICON_FAILED, current=True)
-
- def set_checking_icon(self):
- self.set_item_icon(img=ICON_WAITING, current=True)
-
- def set_checked_icon(self, current=True):
- self.set_item_icon(current=current)
-
- def add_status_line(self, message):
- """
- adds a new status line
- and mark the next-to-last item
- as done
- """
- index = len(self.steps)
- step = ProgressStep(message, False, index=index)
- self.steps.addStep(step)
- self.populateStepsTable()
- self.set_checking_icon()
- self.set_checked_icon(current=False)
-
- # Sets/unsets done flag
- # for isComplete checks
-
- def set_done(self):
- self.done = True
- self.completeChanged.emit()
-
- def set_undone(self):
- self.done = False
- self.completeChanged.emit()
-
- def is_done(self):
- return self.done
-
- # convenience for going back and forth
- # in the wizard pages.
-
- def go_back(self):
- self.wizard().back()
-
- def go_next(self):
- self.wizard().next()
-
-
-"""
-We will use one base class for the intermediate pages
-and another one for the in-page validations, both sharing the creation
-of the tablewidgets.
-The logic of this split comes from where I was trying to solve
-the ui update using signals, but now that it's working well with
-queues I could join them again.
-"""
-
-import Queue
-from functools import partial
-
-
-class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn):
-
- def __init__(self, parent=None):
- super(InlineValidationPage, self).__init__(parent)
- self.setupStepsProcessingQueue()
- self.done = False
-
- # slot
-
- @QtCore.pyqtSlot()
- def showStepsFrame(self):
- self.valFrame.show()
- self.update()
-
- # progress frame
-
- def setupValidationFrame(self):
- qframe = QtGui.QFrame
- valFrame = qframe()
- valFrame.setFrameStyle(qframe.NoFrame)
- valframeLayout = QtGui.QVBoxLayout()
- zeros = (0, 0, 0, 0)
- valframeLayout.setContentsMargins(*zeros)
-
- valframeLayout.addWidget(self.stepsTableWidget)
- valFrame.setLayout(valframeLayout)
- self.valFrame = valFrame
-
-
-class ValidationPage(QtGui.QWizardPage, WithStepsMixIn):
- """
- class to be used as an intermediate
- between two pages in a wizard.
- shows feedback to the user and goes back if errors,
- goes forward if ok.
- initializePage triggers a one shot timer
- that calls do_checks.
- Derived classes should implement
- _do_checks and
- _do_validation
- """
-
- # signals
- stepChanged = QtCore.pyqtSignal([str, int])
-
- def __init__(self, parent=None):
- super(ValidationPage, self).__init__(parent)
- self.setupSteps()
- #self.connect_step_status()
-
- layout = QtGui.QVBoxLayout()
- self.progress = QtGui.QProgressBar(self)
- layout.addWidget(self.progress)
- layout.addWidget(self.stepsTableWidget)
-
- self.setLayout(layout)
- self.layout = layout
-
- self.timer = QtCore.QTimer()
- self.done = False
-
- self.setupStepsProcessingQueue()
-
- def isComplete(self):
- return self.is_done()
-
- ########################
-
- def show_progress(self):
- self.progress.show()
- self.stepsTableWidget.show()
-
- def hide_progress(self):
- self.progress.hide()
- self.stepsTableWidget.hide()
-
- # pagewizard methods.
- # if overriden, child classes should call super.
-
- def initializePage(self):
- self.clean_errors()
- self.clean_wizard_errors()
- self.steps.removeAllSteps()
- self.clearTable()
- self.resizeTable()
- self.timer.singleShot(0, self.do_checks)
diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py
deleted file mode 100644
index b482922e..00000000
--- a/src/leap/gui/styles.py
+++ /dev/null
@@ -1,16 +0,0 @@
-GreenLineEdit = "QLabel {color: green; font-weight: bold}"
-ErrorLabelStyleSheet = """QLabel { color: red; font-weight: bold }"""
-ErrorLineEdit = """QLineEdit { border: 1px solid red; }"""
-
-
-# XXX this is bad.
-# and you should feel bad for it.
-# The original style has a sort of box color
-# white/beige left-top/right-bottom or something like
-# that.
-
-RegularLineEdit = """
-QLineEdit {
- border: 1px solid black;
-}
-"""
diff --git a/src/leap/gui/tests/test_mainwindow_rc.py b/src/leap/gui/test_mainwindow_rc.py
index 67b9fae0..fd02704e 100644
--- a/src/leap/gui/tests/test_mainwindow_rc.py
+++ b/src/leap/gui/test_mainwindow_rc.py
@@ -1,17 +1,14 @@
import unittest
import hashlib
-try:
- import sip
- sip.setapi('QVariant', 2)
-except ValueError:
- pass
+import sip
+sip.setapi('QVariant', 2)
from leap.gui import mainwindow_rc
# I have to admit that there's something
# perverse in testing this.
-# Even though, I still think that it _is_ a good idea
+# But I thought that it could be a good idea
# to put a check to avoid non-updated resources files.
# so, if you came here because an updated resource
@@ -26,7 +23,4 @@ class MainWindowResourcesTest(unittest.TestCase):
def test_mainwindow_resources_hash(self):
self.assertEqual(
hashlib.md5(mainwindow_rc.qt_resource_data).hexdigest(),
- '53e196f29061d8f08f112e5a2e64eb53')
-
-if __name__ == "__main__":
- unittest.main()
+ '5cc26322f96fabaa05c404f22774c716')
diff --git a/src/leap/gui/tests/__init__.py b/src/leap/gui/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/gui/tests/__init__.py
+++ /dev/null
diff --git a/src/leap/gui/tests/integration/fake_user_signup.py b/src/leap/gui/tests/integration/fake_user_signup.py
deleted file mode 100644
index 78873749..00000000
--- a/src/leap/gui/tests/integration/fake_user_signup.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-simple server to test registration and
-authentication
-
-To test:
-
-curl -d login=python_test_user -d password_salt=54321\
- -d password_verifier=12341234 \
- http://localhost:8000/users.json
-
-"""
-from BaseHTTPServer import HTTPServer
-from BaseHTTPServer import BaseHTTPRequestHandler
-import cgi
-import json
-import urlparse
-
-HOST = "localhost"
-PORT = 8000
-
-LOGIN_ERROR = """{"errors":{"login":["has already been taken"]}}"""
-
-from leap.base.tests.test_providers import EXPECTED_DEFAULT_CONFIG
-
-
-class request_handler(BaseHTTPRequestHandler):
- responses = {
- '/': ['ok\n'],
- '/users.json': ['ok\n'],
- '/timeout': ['ok\n'],
- '/provider.json': ['%s\n' % json.dumps(EXPECTED_DEFAULT_CONFIG)]
- }
-
- def do_GET(self):
- path = urlparse.urlparse(self.path)
- message = '\n'.join(
- self.responses.get(
- path.path, None))
- self.send_response(200)
- self.end_headers()
- self.wfile.write(message)
-
- def do_POST(self):
- form = cgi.FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD': 'POST',
- 'CONTENT_TYPE': self.headers['Content-Type'],
- })
- data = dict(
- (key, form[key].value) for key in form.keys())
- path = urlparse.urlparse(self.path)
- message = '\n'.join(
- self.responses.get(
- path.path, ''))
-
- login = data.get('login', None)
- #password_salt = data.get('password_salt', None)
- #password_verifier = data.get('password_verifier', None)
-
- if path.geturl() == "/timeout":
- print 'timeout'
- self.send_response(200)
- self.end_headers()
- self.wfile.write(message)
- import time
- time.sleep(10)
- return
-
- ok = True if (login == "python_test_user") else False
- if ok:
- self.send_response(200)
- self.end_headers()
- self.wfile.write(message)
-
- else:
- self.send_response(500)
- self.end_headers()
- self.wfile.write(LOGIN_ERROR)
-
-
-if __name__ == "__main__":
- server = HTTPServer((HOST, PORT), request_handler)
- server.serve_forever()
diff --git a/src/leap/gui/tests/test_firstrun_login.py b/src/leap/gui/tests/test_firstrun_login.py
deleted file mode 100644
index 6c45b8ef..00000000
--- a/src/leap/gui/tests/test_firstrun_login.py
+++ /dev/null
@@ -1,212 +0,0 @@
-import sys
-import unittest
-
-import mock
-
-from leap.testing import qunittest
-#from leap.testing import pyqt
-
-from PyQt4 import QtGui
-#from PyQt4 import QtCore
-#import PyQt4.QtCore # some weirdness with mock module
-
-from PyQt4.QtTest import QTest
-from PyQt4.QtCore import Qt
-
-from leap.gui import firstrun
-
-try:
- from collections import OrderedDict
-except ImportError:
- # We must be in 2.6
- from leap.util.dicts import OrderedDict
-
-
-class TestPage(firstrun.login.LogInPage):
- pass
-
-
-class LogInPageLogicTestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
- __name__ = "register user page logic tests"
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.page = TestPage(None)
- self.page.wizard = mock.MagicMock()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.page = None
-
- def test__do_checks(self):
- eq = self.assertEqual
-
- self.page.userNameLineEdit.setText('testuser@domain')
- self.page.userPasswordLineEdit.setText('testpassword')
-
- # fake register process
- with mock.patch('leap.base.auth.LeapSRPRegister') as mockAuth:
- mockSignup = mock.MagicMock()
-
- reqMockup = mock.Mock()
- # XXX should inject bad json to get error
- reqMockup.content = '{"errors": null}'
- mockSignup.register_user.return_value = (True, reqMockup)
- mockAuth.return_value = mockSignup
- checks = [x for x in self.page._do_checks()]
-
- eq(len(checks), 4)
- labels = [str(x) for (x, y), z in checks]
- eq(labels, ['head_sentinel',
- 'Resolving domain name',
- 'Validating credentials',
- 'end_sentinel'])
- progress = [y for (x, y), z in checks]
- eq(progress, [0, 20, 60, 100])
-
- # normal run, ie, no exceptions
-
- checkfuns = [z for (x, y), z in checks]
- checkusername, resolvedomain, valcreds = checkfuns[:-1]
-
- self.assertTrue(checkusername())
- #self.mocknetchecker.check_name_resolution.assert_called_with(
- #'test_provider1')
-
- self.assertTrue(resolvedomain())
- #self.mockpcertchecker.is_https_working.assert_called_with(
- #"https://test_provider1", verify=True)
-
- self.assertTrue(valcreds())
-
- # XXX missing: inject failing exceptions
- # XXX TODO make it break
-
-
-class RegisterUserPageUITestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
- __name__ = "Register User Page UI tests"
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
-
- self.pagename = "signup"
- pages = OrderedDict((
- (self.pagename, TestPage),
- ('providersetupvalidation',
- firstrun.connect.ConnectionPage)))
- self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages)
- self.page = self.wizard.page(self.wizard.get_page_index(self.pagename))
-
- self.page.do_checks = mock.Mock()
-
- # wizard would do this for us
- self.page.initializePage()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.wizard = None
-
- # XXX refactor out
- def fill_field(self, field, text):
- """
- fills a field (line edit) that is passed along
- :param field: the qLineEdit
- :param text: the text to be filled
- :type field: QLineEdit widget
- :type text: str
- """
- keyp = QTest.keyPress
- field.setFocus(True)
- for c in text:
- keyp(field, c)
- self.assertEqual(field.text(), text)
-
- def del_field(self, field):
- """
- deletes entried text in
- field line edit
- :param field: the QLineEdit
- :type field: QLineEdit widget
- """
- keyp = QTest.keyPress
- for c in range(len(field.text())):
- keyp(field, Qt.Key_Backspace)
- self.assertEqual(field.text(), "")
-
- def test_buttons_disabled_until_textentry(self):
- # it's a commit button this time
- nextbutton = self.wizard.button(QtGui.QWizard.CommitButton)
-
- self.assertFalse(nextbutton.isEnabled())
-
- f_username = self.page.userNameLineEdit
- f_password = self.page.userPasswordLineEdit
-
- self.fill_field(f_username, "testuser")
- self.fill_field(f_password, "testpassword")
-
- # commit should be enabled
- # XXX Need a workaround here
- # because the isComplete is not being evaluated...
- # (no event loop running??)
- #import ipdb;ipdb.set_trace()
- #self.assertTrue(nextbutton.isEnabled())
- self.assertTrue(self.page.isComplete())
-
- self.del_field(f_username)
- self.del_field(f_password)
-
- # after rm fields commit button
- # should be disabled again
- #self.assertFalse(nextbutton.isEnabled())
- self.assertFalse(self.page.isComplete())
-
- def test_validate_page(self):
- self.assertFalse(self.page.validatePage())
- # XXX TODO MOAR CASES...
- # add errors, False
- # change done, False
- # not done, do_checks called
- # click confirm, True
- # done and do_confirm, True
-
- def test_next_id(self):
- self.assertEqual(self.page.nextId(), 1)
-
- def test_paint_event(self):
- self.page.populateErrors = mock.Mock()
- self.page.paintEvent(None)
- self.page.populateErrors.assert_called_with()
-
- def test_validation_ready(self):
- f_username = self.page.userNameLineEdit
- f_password = self.page.userPasswordLineEdit
-
- self.fill_field(f_username, "testuser")
- self.fill_field(f_password, "testpassword")
-
- self.page.done = True
- self.page.on_checks_validation_ready()
- self.assertFalse(f_username.isEnabled())
- self.assertFalse(f_password.isEnabled())
-
- self.assertEqual(self.page.validationMsg.text(),
- "Credentials validated.")
- self.assertEqual(self.page.do_confirm_next, True)
-
- def test_regex(self):
- # XXX enter invalid username with key presses
- # check text is not updated
- pass
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/tests/test_firstrun_providerselect.py b/src/leap/gui/tests/test_firstrun_providerselect.py
deleted file mode 100644
index 18d89010..00000000
--- a/src/leap/gui/tests/test_firstrun_providerselect.py
+++ /dev/null
@@ -1,203 +0,0 @@
-import sys
-import unittest
-
-import mock
-
-from leap.testing import qunittest
-#from leap.testing import pyqt
-
-from PyQt4 import QtGui
-#from PyQt4 import QtCore
-#import PyQt4.QtCore # some weirdness with mock module
-
-from PyQt4.QtTest import QTest
-from PyQt4.QtCore import Qt
-
-from leap.gui import firstrun
-
-try:
- from collections import OrderedDict
-except ImportError:
- # We must be in 2.6
- from leap.util.dicts import OrderedDict
-
-
-class TestPage(firstrun.providerselect.SelectProviderPage):
- pass
-
-
-class SelectProviderPageLogicTestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.page = TestPage(None)
- self.page.wizard = mock.MagicMock()
-
- mocknetchecker = mock.Mock()
- self.page.wizard().netchecker.return_value = mocknetchecker
- self.mocknetchecker = mocknetchecker
-
- mockpcertchecker = mock.Mock()
- self.page.wizard().providercertchecker.return_value = mockpcertchecker
- self.mockpcertchecker = mockpcertchecker
-
- mockeipconfchecker = mock.Mock()
- self.page.wizard().eipconfigchecker.return_value = mockeipconfchecker
- self.mockeipconfchecker = mockeipconfchecker
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.page = None
-
- def test__do_checks(self):
- eq = self.assertEqual
-
- self.page.providerNameEdit.setText('test_provider1')
-
- checks = [x for x in self.page._do_checks()]
- eq(len(checks), 5)
- labels = [str(x) for (x, y), z in checks]
- eq(labels, ['head_sentinel',
- 'Checking if it is a valid provider',
- 'Checking for a secure connection',
- 'Getting info from the provider',
- 'end_sentinel'])
- progress = [y for (x, y), z in checks]
- eq(progress, [0, 20, 40, 80, 100])
-
- # normal run, ie, no exceptions
-
- checkfuns = [z for (x, y), z in checks]
- namecheck, httpscheck, fetchinfo = checkfuns[1:-1]
-
- self.assertTrue(namecheck())
- self.mocknetchecker.check_name_resolution.assert_called_with(
- 'test_provider1')
-
- self.assertTrue(httpscheck())
- self.mockpcertchecker.is_https_working.assert_called_with(
- "https://test_provider1", verify=True)
-
- self.assertTrue(fetchinfo())
- self.mockeipconfchecker.fetch_definition.assert_called_with(
- domain="test_provider1")
-
- # XXX missing: inject failing exceptions
- # XXX TODO make it break
-
-
-class SelectProviderPageUITestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
- __name__ = "Select Provider Page UI tests"
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
-
- self.pagename = "providerselection"
- pages = OrderedDict((
- (self.pagename, TestPage),
- ('providerinfo',
- firstrun.providerinfo.ProviderInfoPage)))
- self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages)
- self.page = self.wizard.page(self.wizard.get_page_index(self.pagename))
-
- self.page.do_checks = mock.Mock()
-
- # wizard would do this for us
- self.page.initializePage()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.wizard = None
-
- def fill_provider(self):
- """
- fills provider line edit
- """
- keyp = QTest.keyPress
- pedit = self.page.providerNameEdit
- pedit.setFocus(True)
- for c in "testprovider":
- keyp(pedit, c)
- self.assertEqual(pedit.text(), "testprovider")
-
- def del_provider(self):
- """
- deletes entried provider in
- line edit
- """
- keyp = QTest.keyPress
- pedit = self.page.providerNameEdit
- for c in range(len("testprovider")):
- keyp(pedit, Qt.Key_Backspace)
- self.assertEqual(pedit.text(), "")
-
- def test_buttons_disabled_until_textentry(self):
- nextbutton = self.wizard.button(QtGui.QWizard.NextButton)
- checkbutton = self.page.providerCheckButton
-
- self.assertFalse(nextbutton.isEnabled())
- self.assertFalse(checkbutton.isEnabled())
-
- self.fill_provider()
- # checkbutton should be enabled
- self.assertTrue(checkbutton.isEnabled())
- self.assertFalse(nextbutton.isEnabled())
-
- self.del_provider()
- # after rm provider checkbutton disabled again
- self.assertFalse(checkbutton.isEnabled())
- self.assertFalse(nextbutton.isEnabled())
-
- def test_check_button_triggers_tests(self):
- checkbutton = self.page.providerCheckButton
- self.assertFalse(checkbutton.isEnabled())
- self.assertFalse(self.page.do_checks.called)
-
- self.fill_provider()
-
- self.assertTrue(checkbutton.isEnabled())
- mclick = QTest.mouseClick
- # click!
- mclick(checkbutton, Qt.LeftButton)
- self.waitFor(seconds=0.1)
- self.assertTrue(self.page.do_checks.called)
-
- # XXX
- # can play with different side_effects for do_checks mock...
- # so we can see what happens with errors and so on
-
- def test_page_completed_after_checks(self):
- nextbutton = self.wizard.button(QtGui.QWizard.NextButton)
- self.assertFalse(nextbutton.isEnabled())
-
- self.assertFalse(self.page.isComplete())
- self.fill_provider()
- # simulate checks done
- self.page.done = True
- self.page.on_checks_validation_ready()
- self.assertTrue(self.page.isComplete())
- # cannot test for nexbutton enabled
- # cause it's the the wizard loop
- # that would do that I think
-
- def test_validate_page(self):
- self.assertTrue(self.page.validatePage())
-
- def test_next_id(self):
- self.assertEqual(self.page.nextId(), 1)
-
- def test_paint_event(self):
- self.page.populateErrors = mock.Mock()
- self.page.paintEvent(None)
- self.page.populateErrors.assert_called_with()
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/tests/test_firstrun_register.py b/src/leap/gui/tests/test_firstrun_register.py
deleted file mode 100644
index 9d62f808..00000000
--- a/src/leap/gui/tests/test_firstrun_register.py
+++ /dev/null
@@ -1,244 +0,0 @@
-import sys
-import unittest
-
-import mock
-
-from leap.testing import qunittest
-#from leap.testing import pyqt
-
-from PyQt4 import QtGui
-#from PyQt4 import QtCore
-#import PyQt4.QtCore # some weirdness with mock module
-
-from PyQt4.QtTest import QTest
-from PyQt4.QtCore import Qt
-
-from leap.gui import firstrun
-
-try:
- from collections import OrderedDict
-except ImportError:
- # We must be in 2.6
- from leap.util.dicts import OrderedDict
-
-
-class TestPage(firstrun.register.RegisterUserPage):
-
- def field(self, field):
- if field == "provider_domain":
- return "testprovider"
-
-
-class RegisterUserPageLogicTestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
- __name__ = "register user page logic tests"
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.page = TestPage(None)
- self.page.wizard = mock.MagicMock()
-
- #mocknetchecker = mock.Mock()
- #self.page.wizard().netchecker.return_value = mocknetchecker
- #self.mocknetchecker = mocknetchecker
-#
- #mockpcertchecker = mock.Mock()
- #self.page.wizard().providercertchecker.return_value = mockpcertchecker
- #self.mockpcertchecker = mockpcertchecker
-#
- #mockeipconfchecker = mock.Mock()
- #self.page.wizard().eipconfigchecker.return_value = mockeipconfchecker
- #self.mockeipconfchecker = mockeipconfchecker
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.page = None
-
- def test__do_checks(self):
- eq = self.assertEqual
-
- self.page.userNameLineEdit.setText('testuser')
- self.page.userPasswordLineEdit.setText('testpassword')
- self.page.userPassword2LineEdit.setText('testpassword')
-
- # fake register process
- with mock.patch('leap.base.auth.LeapSRPRegister') as mockAuth:
- mockSignup = mock.MagicMock()
-
- reqMockup = mock.Mock()
- # XXX should inject bad json to get error
- reqMockup.content = '{"errors": null}'
- mockSignup.register_user.return_value = (True, reqMockup)
- mockAuth.return_value = mockSignup
- checks = [x for x in self.page._do_checks()]
-
- eq(len(checks), 3)
- labels = [str(x) for (x, y), z in checks]
- eq(labels, ['head_sentinel',
- 'Registering username',
- 'end_sentinel'])
- progress = [y for (x, y), z in checks]
- eq(progress, [0, 40, 100])
-
- # normal run, ie, no exceptions
-
- checkfuns = [z for (x, y), z in checks]
- passcheck, register = checkfuns[:-1]
-
- self.assertTrue(passcheck())
- #self.mocknetchecker.check_name_resolution.assert_called_with(
- #'test_provider1')
-
- self.assertTrue(register())
- #self.mockpcertchecker.is_https_working.assert_called_with(
- #"https://test_provider1", verify=True)
-
- # XXX missing: inject failing exceptions
- # XXX TODO make it break
-
-
-class RegisterUserPageUITestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
- __name__ = "Register User Page UI tests"
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
-
- self.pagename = "signup"
- pages = OrderedDict((
- (self.pagename, TestPage),
- ('connect',
- firstrun.connect.ConnectionPage)))
- self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages)
- self.page = self.wizard.page(self.wizard.get_page_index(self.pagename))
-
- self.page.do_checks = mock.Mock()
-
- # wizard would do this for us
- self.page.initializePage()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.wizard = None
-
- def fill_field(self, field, text):
- """
- fills a field (line edit) that is passed along
- :param field: the qLineEdit
- :param text: the text to be filled
- :type field: QLineEdit widget
- :type text: str
- """
- keyp = QTest.keyPress
- field.setFocus(True)
- for c in text:
- keyp(field, c)
- self.assertEqual(field.text(), text)
-
- def del_field(self, field):
- """
- deletes entried text in
- field line edit
- :param field: the QLineEdit
- :type field: QLineEdit widget
- """
- keyp = QTest.keyPress
- for c in range(len(field.text())):
- keyp(field, Qt.Key_Backspace)
- self.assertEqual(field.text(), "")
-
- def test_buttons_disabled_until_textentry(self):
- # it's a commit button this time
- nextbutton = self.wizard.button(QtGui.QWizard.CommitButton)
-
- self.assertFalse(nextbutton.isEnabled())
-
- f_username = self.page.userNameLineEdit
- f_password = self.page.userPasswordLineEdit
- f_passwor2 = self.page.userPassword2LineEdit
-
- self.fill_field(f_username, "testuser")
- self.fill_field(f_password, "testpassword")
- self.fill_field(f_passwor2, "testpassword")
-
- # commit should be enabled
- # XXX Need a workaround here
- # because the isComplete is not being evaluated...
- # (no event loop running??)
- #import ipdb;ipdb.set_trace()
- #self.assertTrue(nextbutton.isEnabled())
- self.assertTrue(self.page.isComplete())
-
- self.del_field(f_username)
- self.del_field(f_password)
- self.del_field(f_passwor2)
-
- # after rm fields commit button
- # should be disabled again
- #self.assertFalse(nextbutton.isEnabled())
- self.assertFalse(self.page.isComplete())
-
- @unittest.skip
- def test_check_button_triggers_tests(self):
- checkbutton = self.page.providerCheckButton
- self.assertFalse(checkbutton.isEnabled())
- self.assertFalse(self.page.do_checks.called)
-
- self.fill_provider()
-
- self.assertTrue(checkbutton.isEnabled())
- mclick = QTest.mouseClick
- # click!
- mclick(checkbutton, Qt.LeftButton)
- self.waitFor(seconds=0.1)
- self.assertTrue(self.page.do_checks.called)
-
- # XXX
- # can play with different side_effects for do_checks mock...
- # so we can see what happens with errors and so on
-
- def test_validate_page(self):
- self.assertFalse(self.page.validatePage())
- # XXX TODO MOAR CASES...
- # add errors, False
- # change done, False
- # not done, do_checks called
- # click confirm, True
- # done and do_confirm, True
-
- def test_next_id(self):
- self.assertEqual(self.page.nextId(), 1)
-
- def test_paint_event(self):
- self.page.populateErrors = mock.Mock()
- self.page.paintEvent(None)
- self.page.populateErrors.assert_called_with()
-
- def test_validation_ready(self):
- f_username = self.page.userNameLineEdit
- f_password = self.page.userPasswordLineEdit
- f_passwor2 = self.page.userPassword2LineEdit
-
- self.fill_field(f_username, "testuser")
- self.fill_field(f_password, "testpassword")
- self.fill_field(f_passwor2, "testpassword")
-
- self.page.done = True
- self.page.on_checks_validation_ready()
- self.assertFalse(f_username.isEnabled())
- self.assertFalse(f_password.isEnabled())
- self.assertFalse(f_passwor2.isEnabled())
-
- self.assertEqual(self.page.validationMsg.text(),
- "Registration succeeded!")
- self.assertEqual(self.page.do_confirm_next, True)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/tests/test_firstrun_wizard.py b/src/leap/gui/tests/test_firstrun_wizard.py
deleted file mode 100644
index 395604d3..00000000
--- a/src/leap/gui/tests/test_firstrun_wizard.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import sys
-import unittest
-
-import mock
-
-from leap.testing import qunittest
-from leap.testing import pyqt
-
-from PyQt4 import QtGui
-#from PyQt4 import QtCore
-import PyQt4.QtCore # some weirdness with mock module
-
-from PyQt4.QtTest import QTest
-#from PyQt4.QtCore import Qt
-
-from leap.gui import firstrun
-
-
-class TestWizard(firstrun.wizard.FirstRunWizard):
- pass
-
-
-PAGES_DICT = dict((
- ('intro', firstrun.intro.IntroPage),
- ('providerselection',
- firstrun.providerselect.SelectProviderPage),
- ('login', firstrun.login.LogInPage),
- ('providerinfo', firstrun.providerinfo.ProviderInfoPage),
- ('providersetupvalidation',
- firstrun.providersetup.ProviderSetupValidationPage),
- ('signup', firstrun.register.RegisterUserPage),
- ('connect',
- firstrun.connect.ConnectionPage),
- ('lastpage', firstrun.last.LastPage)
-))
-
-
-mockQSettings = mock.MagicMock()
-mockQSettings().setValue.return_value = True
-
-#PyQt4.QtCore.QSettings = mockQSettings
-
-
-class FirstRunWizardTestCase(qunittest.TestCase):
-
- # XXX can spy on signal connections
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.wizard = TestWizard(None)
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
- self.wizard = None
-
- def test_defaults(self):
- self.assertEqual(self.wizard.pages_dict, PAGES_DICT)
-
- @mock.patch('PyQt4.QtCore.QSettings', mockQSettings)
- def test_accept(self):
- """
- test the main accept method
- that gets called when user has gone
- thru all the wizard and click on finish button
- """
-
- self.wizard.success_cb = mock.Mock()
- self.wizard.success_cb.return_value = True
-
- # dummy values; we inject them in the field
- # mocks (where wizard gets them) and then
- # we check that they are passed to QSettings.setValue
- field_returns = ["testuser", "1234", "testprovider", True]
-
- def field_side_effects(*args):
- return field_returns.pop(0)
-
- self.wizard.field = mock.Mock(side_effect=field_side_effects)
- self.wizard.get_random_str = mock.Mock()
- RANDOMSTR = "thisisarandomstringTM"
- self.wizard.get_random_str.return_value = RANDOMSTR
-
- # mocked settings (see decorator on this method)
- mqs = PyQt4.QtCore.QSettings
-
- # go! call accept...
- self.wizard.accept()
-
- # did settings().setValue get called with the proper
- # arguments?
- call = mock.call
- calls = [call("FirstRunWizardDone", True),
- call("provider_domain", "testprovider"),
- call("remember_user_and_pass", True),
- call("username", "testuser@testprovider"),
- call("testprovider_seed", RANDOMSTR)]
- mqs().setValue.assert_has_calls(calls, any_order=True)
-
- # assert success callback is success oh boy
- self.wizard.success_cb.assert_called_with()
-
- def test_random_str(self):
- r = self.wizard.get_random_str(42)
- self.assertTrue(len(r) == 42)
-
- def test_page_index(self):
- """
- we test both the get_page_index function
- and the correct ordering of names
- """
- # remember it's implemented as an ordered dict
-
- pagenames = ('intro', 'providerselection', 'login', 'providerinfo',
- 'providersetupvalidation', 'signup', 'connect',
- 'lastpage')
- eq = self.assertEqual
- w = self.wizard
- for index, name in enumerate(pagenames):
- eq(w.get_page_index(name), index)
-
- def test_validation_errors(self):
- """
- tests getters and setters for validation errors
- """
- page = "testpage"
- eq = self.assertEqual
- w = self.wizard
- eq(w.get_validation_error(page), None)
- w.set_validation_error(page, "error")
- eq(w.get_validation_error(page), "error")
- w.clean_validation_error(page)
- eq(w.get_validation_error(page), None)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/tests/test_progress.py b/src/leap/gui/tests/test_progress.py
deleted file mode 100644
index 1f9f9e38..00000000
--- a/src/leap/gui/tests/test_progress.py
+++ /dev/null
@@ -1,449 +0,0 @@
-from collections import namedtuple
-import sys
-import unittest
-import Queue
-
-import mock
-
-from leap.testing import qunittest
-from leap.testing import pyqt
-
-from PyQt4 import QtGui
-from PyQt4 import QtCore
-from PyQt4.QtTest import QTest
-from PyQt4.QtCore import Qt
-
-from leap.gui import progress
-
-
-class ProgressStepTestCase(unittest.TestCase):
-
- def test_step_attrs(self):
- ps = progress.ProgressStep
- step = ps('test', False, 1)
- # instance
- self.assertEqual(step.index, 1)
- self.assertEqual(step.name, "test")
- self.assertEqual(step.done, False)
- step = ps('test2', True, 2)
- self.assertEqual(step.index, 2)
- self.assertEqual(step.name, "test2")
- self.assertEqual(step.done, True)
-
- # class methods and attrs
- self.assertEqual(ps.columns(), ('name', 'done'))
- self.assertEqual(ps.NAME, 0)
- self.assertEqual(ps.DONE, 1)
-
-
-class ProgressStepContainerTestCase(unittest.TestCase):
- def setUp(self):
- self.psc = progress.ProgressStepContainer()
-
- def addSteps(self, number):
- Step = progress.ProgressStep
- for n in range(number):
- self.psc.addStep(Step("%s" % n, False, n))
-
- def test_attrs(self):
- self.assertEqual(self.psc.columns,
- ('name', 'done'))
-
- def test_add_steps(self):
- Step = progress.ProgressStep
- self.assertTrue(len(self.psc) == 0)
- self.psc.addStep(Step('one', False, 0))
- self.assertTrue(len(self.psc) == 1)
- self.psc.addStep(Step('two', False, 1))
- self.assertTrue(len(self.psc) == 2)
-
- def test_del_all_steps(self):
- self.assertTrue(len(self.psc) == 0)
- self.addSteps(5)
- self.assertTrue(len(self.psc) == 5)
- self.psc.removeAllSteps()
- self.assertTrue(len(self.psc) == 0)
-
- def test_del_step(self):
- Step = progress.ProgressStep
- self.addSteps(5)
- self.assertTrue(len(self.psc) == 5)
- self.psc.removeStep(self.psc.step(4))
- self.assertTrue(len(self.psc) == 4)
- self.psc.removeStep(self.psc.step(4))
- self.psc.removeStep(Step('none', False, 5))
- self.psc.removeStep(self.psc.step(4))
-
- def test_iter(self):
- self.addSteps(10)
- self.assertEqual(
- [x.index for x in self.psc],
- [x for x in range(10)])
-
-
-class StepsTableWidgetTestCase(unittest.TestCase):
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.stw = progress.StepsTableWidget()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
-
- def test_defaults(self):
- self.assertTrue(isinstance(self.stw, QtGui.QTableWidget))
- self.assertEqual(self.stw.focusPolicy(), 0)
-
-
-class TestWithStepsClass(QtGui.QWidget, progress.WithStepsMixIn):
-
- def __init__(self, parent=None):
- super(TestWithStepsClass, self).__init__(parent=parent)
- self.setupStepsProcessingQueue()
- self.statuses = []
- self.current_page = "testpage"
-
- def onStepStatusChanged(self, *args):
- """
- blank out this gui method
- that will add status lines
- """
- self.statuses.append(args)
-
-
-class WithStepsMixInTestCase(qunittest.TestCase):
-
- TIMER_WAIT = 2 * progress.WithStepsMixIn.STEPS_TIMER_MS / 1000.0
-
- # XXX can spy on signal connections
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.stepy = TestWithStepsClass()
- #self.connects = []
- #pyqt.enableSignalDebugging(
- #connectCall=lambda *args: self.connects.append(args))
- #self.assertEqual(self.connects, [])
- #self.stepy.stepscheck_timer.timeout.disconnect(
- #self.stepy.processStepsQueue)
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
-
- def test_has_queue(self):
- s = self.stepy
- self.assertTrue(hasattr(s, 'steps_queue'))
- self.assertTrue(isinstance(s.steps_queue, Queue.Queue))
- self.assertTrue(isinstance(s.stepscheck_timer, QtCore.QTimer))
-
- def test_do_checks_delegation(self):
- s = self.stepy
-
- _do_checks = mock.Mock()
- _do_checks.return_value = (
- (("test", 0), lambda: None),
- (("test", 0), lambda: None))
- s._do_checks = _do_checks
- s.do_checks()
- self.waitFor(seconds=self.TIMER_WAIT)
- _do_checks.assert_called_with()
- self.assertEqual(len(s.statuses), 2)
-
- # test that a failed test interrupts the run
-
- s.statuses = []
- _do_checks = mock.Mock()
- _do_checks.return_value = (
- (("test", 0), lambda: None),
- (("test", 0), lambda: False),
- (("test", 0), lambda: None))
- s._do_checks = _do_checks
- s.do_checks()
- self.waitFor(seconds=self.TIMER_WAIT)
- _do_checks.assert_called_with()
- self.assertEqual(len(s.statuses), 2)
-
- def test_process_queue(self):
- s = self.stepy
- q = s.steps_queue
- s.set_failed_icon = mock.MagicMock()
- with self.assertRaises(AssertionError):
- q.put('foo')
- self.waitFor(seconds=self.TIMER_WAIT)
- s.set_failed_icon.assert_called_with()
- q.put("failed")
- self.waitFor(seconds=self.TIMER_WAIT)
- s.set_failed_icon.assert_called_with()
-
- def test_on_checks_validation_ready_called(self):
- s = self.stepy
- s.on_checks_validation_ready = mock.MagicMock()
-
- _do_checks = mock.Mock()
- _do_checks.return_value = (
- (("test", 0), lambda: None),)
- s._do_checks = _do_checks
- s.do_checks()
-
- self.waitFor(seconds=self.TIMER_WAIT)
- s.on_checks_validation_ready.assert_called_with()
-
- def test_fail(self):
- s = self.stepy
-
- s.wizard = mock.Mock()
- wizard = s.wizard.return_value
- wizard.set_validation_error.return_value = True
- s.completeChanged = mock.Mock()
- s.completeChanged.emit.return_value = True
-
- self.assertFalse(s.fail(err="foo"))
- self.waitFor(seconds=self.TIMER_WAIT)
- wizard.set_validation_error.assert_called_with('testpage', 'foo')
- s.completeChanged.emit.assert_called_with()
-
- # with no args
- s.wizard = mock.Mock()
- wizard = s.wizard.return_value
- wizard.set_validation_error.return_value = True
- s.completeChanged = mock.Mock()
- s.completeChanged.emit.return_value = True
-
- self.assertFalse(s.fail())
- self.waitFor(seconds=self.TIMER_WAIT)
- with self.assertRaises(AssertionError):
- wizard.set_validation_error.assert_called_with()
- s.completeChanged.emit.assert_called_with()
-
- def test_done(self):
- s = self.stepy
- s.done = False
-
- s.completeChanged = mock.Mock()
- s.completeChanged.emit.return_value = True
-
- self.assertFalse(s.is_done())
- s.set_done()
- self.assertTrue(s.is_done())
- s.completeChanged.emit.assert_called_with()
-
- s.completeChanged = mock.Mock()
- s.completeChanged.emit.return_value = True
- s.set_undone()
- self.assertFalse(s.is_done())
-
- def test_back_and_next(self):
- s = self.stepy
- s.wizard = mock.Mock()
- wizard = s.wizard.return_value
- wizard.back.return_value = True
- wizard.next.return_value = True
- s.go_back()
- wizard.back.assert_called_with()
- s.go_next()
- wizard.next.assert_called_with()
-
- def test_on_step_statuschanged_slot(self):
- s = self.stepy
- s.onStepStatusChanged = progress.WithStepsMixIn.onStepStatusChanged
- s.add_status_line = mock.Mock()
- s.set_checked_icon = mock.Mock()
- s.progress = mock.Mock()
- s.progress.setValue.return_value = True
- s.progress.update.return_value = True
-
- s.onStepStatusChanged(s, "end_sentinel")
- s.set_checked_icon.assert_called_with()
-
- s.onStepStatusChanged(s, "foo")
- s.add_status_line.assert_called_with("foo")
-
- s.onStepStatusChanged(s, "bar", 42)
- s.progress.setValue.assert_called_with(42)
- s.progress.update.assert_called_with()
-
- def test_steps_and_errors(self):
- s = self.stepy
- s.setupSteps()
- self.assertTrue(isinstance(s.steps, progress.ProgressStepContainer))
- self.assertEqual(s.errors, {})
- s.set_error('fooerror', 'barerror')
- self.assertEqual(s.errors, {'fooerror': 'barerror'})
- s.set_error('2', 42)
- self.assertEqual(s.errors, {'fooerror': 'barerror', '2': 42})
- fe = s.pop_first_error()
- self.assertEqual(fe, ('fooerror', 'barerror'))
- self.assertEqual(s.errors, {'2': 42})
- s.clean_errors()
- self.assertEqual(s.errors, {})
-
- def test_launch_chechs_slot(self):
- s = self.stepy
- s.do_checks = mock.Mock()
- s.launch_checks()
- s.do_checks.assert_called_with()
-
- def test_clean_wizard_errors(self):
- s = self.stepy
- s.wizard = mock.Mock()
- wizard = s.wizard.return_value
- wizard.set_validation_error.return_value = True
- s.clean_wizard_errors(pagename="foopage")
- wizard.set_validation_error.assert_called_with("foopage", None)
-
- def test_clear_table(self):
- s = self.stepy
- s.stepsTableWidget = mock.Mock()
- s.stepsTableWidget.clearContents.return_value = True
- s.clearTable()
- s.stepsTableWidget.clearContents.assert_called_with()
-
- def test_populate_steps_table(self):
- s = self.stepy
- Step = namedtuple('Step', ['name', 'done'])
-
- class Steps(object):
- columns = ("name", "done")
- _items = (Step('step1', False), Step('step2', False))
-
- def __len__(self):
- return 2
-
- def __iter__(self):
- for i in self._items:
- yield i
-
- s.steps = Steps()
-
- s.stepsTableWidget = mock.Mock()
- s.stepsTableWidget.setItem.return_value = True
- s.resizeTable = mock.Mock()
- s.update = mock.Mock()
- s.populateStepsTable()
- s.update.assert_called_with()
- s.resizeTable.assert_called_with()
-
- # assert stepsTableWidget.setItem called ...
- # we do not want to get into the actual
- # <QTableWidgetItem object at 0x92a565c>
- call_list = s.stepsTableWidget.setItem.call_args_list
- indexes = [(y, z) for y, z, xx in [x[0] for x in call_list]]
- self.assertEqual(indexes,
- [(0, 0), (0, 1), (1, 0), (1, 1)])
-
- def test_add_status_line(self):
- s = self.stepy
- s.steps = progress.ProgressStepContainer()
- s.stepsTableWidget = mock.Mock()
- s.stepsTableWidget.width.return_value = 100
- s.set_item = mock.Mock()
- s.set_item_icon = mock.Mock()
- s.add_status_line("new status")
- s.set_item_icon.assert_called_with(current=False)
-
- def test_set_item_icon(self):
- s = self.stepy
- s.steps = progress.ProgressStepContainer()
- s.stepsTableWidget = mock.Mock()
- s.stepsTableWidget.setCellWidget.return_value = True
- s.stepsTableWidget.width.return_value = 100
- #s.set_item = mock.Mock()
- #s.set_item_icon = mock.Mock()
- s.add_status_line("new status")
- s.add_status_line("new 2 status")
- s.add_status_line("new 3 status")
- call_list = s.stepsTableWidget.setCellWidget.call_args_list
- indexes = [(y, z) for y, z, xx in [x[0] for x in call_list]]
- self.assertEqual(
- indexes,
- [(0, 1), (-1, 1), (1, 1), (0, 1), (2, 1), (1, 1)])
-
-
-class TestInlineValidationPage(progress.InlineValidationPage):
- pass
-
-
-class InlineValidationPageTestCase(unittest.TestCase):
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.page = TestInlineValidationPage()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
-
- def test_defaults(self):
- self.assertFalse(self.page.done)
- # if setupProcessingQueue was called
- self.assertTrue(isinstance(self.page.stepscheck_timer, QtCore.QTimer))
- self.assertTrue(isinstance(self.page.steps_queue, Queue.Queue))
-
- def test_validation_frame(self):
- # test frame creation
- self.page.stepsTableWidget = progress.StepsTableWidget(
- parent=self.page)
- self.page.setupValidationFrame()
- self.assertTrue(isinstance(self.page.valFrame, QtGui.QFrame))
-
- # test show steps calls frame.show
- self.page.valFrame = mock.Mock()
- self.page.valFrame.show.return_value = True
- self.page.showStepsFrame()
- self.page.valFrame.show.assert_called_with()
-
-
-class TestValidationPage(progress.ValidationPage):
- pass
-
-
-class ValidationPageTestCase(unittest.TestCase):
-
- def setUp(self):
- self.app = QtGui.QApplication(sys.argv)
- QtGui.qApp = self.app
- self.page = TestValidationPage()
-
- def tearDown(self):
- QtGui.qApp = None
- self.app = None
-
- def test_defaults(self):
- self.assertFalse(self.page.done)
- # if setupProcessingQueue was called
- self.assertTrue(isinstance(self.page.timer, QtCore.QTimer))
- self.assertTrue(isinstance(self.page.stepscheck_timer, QtCore.QTimer))
- self.assertTrue(isinstance(self.page.steps_queue, Queue.Queue))
-
- def test_is_complete(self):
- self.assertFalse(self.page.isComplete())
- self.page.done = True
- self.assertTrue(self.page.isComplete())
- self.page.done = False
- self.assertFalse(self.page.isComplete())
-
- def test_show_hide_progress(self):
- p = self.page
- p.progress = mock.Mock()
- p.progress.show.return_code = True
- p.show_progress()
- p.progress.show.assert_called_with()
- p.progress.hide.return_code = True
- p.hide_progress()
- p.progress.hide.assert_called_with()
-
- def test_initialize_page(self):
- p = self.page
- p.timer = mock.Mock()
- p.timer.singleShot.return_code = True
- p.initializePage()
- p.timer.singleShot.assert_called_with(0, p.do_checks)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/tests/test_threads.py b/src/leap/gui/tests/test_threads.py
deleted file mode 100644
index 06c19606..00000000
--- a/src/leap/gui/tests/test_threads.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import unittest
-
-import mock
-from leap.gui import threads
-
-
-class FunThreadTestCase(unittest.TestCase):
-
- def setUp(self):
- self.fun = mock.MagicMock()
- self.fun.return_value = "foo"
- self.t = threads.FunThread(fun=self.fun)
-
- def test_thread(self):
- self.t.begin()
- self.t.wait()
- self.fun.assert_called()
- del self.t
-
- def test_run(self):
- # this is called by PyQt
- self.t.run()
- del self.t
- self.fun.assert_called()
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/gui/threads.py b/src/leap/gui/threads.py
deleted file mode 100644
index 8aad8866..00000000
--- a/src/leap/gui/threads.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from PyQt4 import QtCore
-
-
-class FunThread(QtCore.QThread):
-
- def __init__(self, fun=None, parent=None):
-
- QtCore.QThread.__init__(self, parent)
- self.exiting = False
- self.fun = fun
-
- def __del__(self):
- self.exiting = True
- self.wait()
-
- def run(self):
- if self.fun:
- self.fun()
-
- def begin(self):
- self.start()
diff --git a/src/leap/gui/utils.py b/src/leap/gui/utils.py
deleted file mode 100644
index f91ac3ef..00000000
--- a/src/leap/gui/utils.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-utility functions to work with gui objects
-"""
-from PyQt4 import QtCore
-
-
-def layout_widgets(layout):
- """
- return a generator with all widgets in a layout
- """
- return (layout.itemAt(i) for i in range(layout.count()))
-
-
-DELAY_MSECS = 50
-
-
-def delay(obj, method_str=None, call_args=None):
- """
- Triggers a function or slot with a small delay.
- this is a mainly a hack to get responsiveness in the ui
- in cases in which the event loop freezes and the task
- is not heavy enough to setup a processing queue.
- """
- if callable(obj) and not method_str:
- fun = lambda: obj()
-
- if method_str:
- invoke = QtCore.QMetaObject.invokeMethod
- if call_args:
- fun = lambda: invoke(obj, method_str, call_args)
- else:
- fun = lambda: invoke(obj, method_str)
-
- QtCore.QTimer().singleShot(DELAY_MSECS, fun)
diff --git a/src/leap/testing/__init__.py b/src/leap/testing/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/testing/__init__.py
+++ /dev/null
diff --git a/src/leap/testing/basetest.py b/src/leap/testing/basetest.py
deleted file mode 100644
index 3186e1eb..00000000
--- a/src/leap/testing/basetest.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import os
-import platform
-import shutil
-import tempfile
-
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-from leap.base.config import get_username, get_groupname
-from leap.util.fileutil import mkdir_p, check_and_fix_urw_only
-
-_system = platform.system()
-
-
-class BaseLeapTest(unittest.TestCase):
-
- __name__ = "leap_test"
-
- @classmethod
- def setUpClass(cls):
- cls.old_path = os.environ['PATH']
- cls.old_home = os.environ['HOME']
- cls.tempdir = tempfile.mkdtemp(prefix="leap_tests-")
- cls.home = cls.tempdir
- bin_tdir = os.path.join(
- cls.tempdir,
- 'bin')
- os.environ["PATH"] = bin_tdir
- os.environ["HOME"] = cls.tempdir
-
- @classmethod
- def tearDownClass(cls):
- os.environ["PATH"] = cls.old_path
- os.environ["HOME"] = cls.old_home
- # safety check
- assert cls.tempdir.startswith('/tmp/leap_tests-')
- shutil.rmtree(cls.tempdir)
-
- # you have to override these methods
- # this way we ensure we did not put anything
- # here that you can forget to call.
-
- def setUp(self):
- raise NotImplementedError("abstract base class")
-
- def tearDown(self):
- raise NotImplementedError("abstract base class")
-
- #
- # helper methods
- #
-
- def get_tempfile(self, filename):
- return os.path.join(self.tempdir, filename)
-
- def get_username(self):
- return get_username()
-
- def get_groupname(self):
- return get_groupname()
-
- def _missing_test_for_plat(self, do_raise=False):
- if do_raise:
- raise NotImplementedError(
- "This test is not implemented "
- "for the running platform: %s" %
- _system)
-
- def touch(self, filepath):
- folder, filename = os.path.split(filepath)
- if not os.path.isdir(folder):
- mkdir_p(folder)
- # XXX should move to test_basetest
- self.assertTrue(os.path.isdir(folder))
-
- with open(filepath, 'w') as fp:
- fp.write(' ')
-
- # XXX should move to test_basetest
- self.assertTrue(os.path.isfile(filepath))
-
- def chmod600(self, filepath):
- check_and_fix_urw_only(filepath)
diff --git a/src/leap/testing/cacert.pem b/src/leap/testing/cacert.pem
deleted file mode 100644
index 6989c480..00000000
--- a/src/leap/testing/cacert.pem
+++ /dev/null
@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID1TCCAr2gAwIBAgIJAOv0BS09D8byMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD
-VQQGEwJVUzETMBEGA1UECAwKY3liZXJzcGFjZTEnMCUGA1UECgweTEVBUCBFbmNy
-eXB0aW9uIEFjY2VzcyBQcm9qZWN0MRYwFAYDVQQDDA10ZXN0cy1sZWFwLnNlMRsw
-GQYJKoZIhvcNAQkBFgxpbmZvQGxlYXAuc2UwHhcNMTIwODMxMTYyNjMwWhcNMTUw
-ODMxMTYyNjMwWjCBgDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmN5YmVyc3BhY2Ux
-JzAlBgNVBAoMHkxFQVAgRW5jcnlwdGlvbiBBY2Nlc3MgUHJvamVjdDEWMBQGA1UE
-AwwNdGVzdHMtbGVhcC5zZTEbMBkGCSqGSIb3DQEJARYMaW5mb0BsZWFwLnNlMIIB
-IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1pU7OU+abrUXFZwp6X0LlF0f
-xQvC1Nmr5sFH7N9RTu3bdwY2t57ECP2TPkH6+x7oOvCTgAMxIE1scWEEkfgKViqW
-FH/Om1UW1PMaiDYGtFuqEuxM95FvaYxp2K6rzA37WNsedA28sCYzhRD+/5HqbCNT
-3rRS2cPaVO8kXI/5bgd8bUk3009pWTg4SvTtOW/9MWJbBH5f5JWmMn7Ayt6hIdT/
-E6npofEK/UCqAlEscARYFXSB/F8nK1whjo9mGFjMUd7d/25UbFHqOk4K7ishD4DH
-F7LaS84rS+Sjwn3YtDdDQblGghJfz8X1AfPSGivGnvLVdkmMF9Y2hJlSQ7+C5wID
-AQABo1AwTjAdBgNVHQ4EFgQUnpJEv4FnlqKbfm7mprudKdrnOAowHwYDVR0jBBgw
-FoAUnpJEv4FnlqKbfm7mprudKdrnOAowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
-AQUFAAOCAQEAGW66qwdK/ATRVZkTpI2sgi+2dWD5tY4VyZuJIrRwfXsGPeVvmdsa
-zDmwW5dMkth1Of5yO6o7ijvUvfnw/UCLNLNICKZhH5G0DHstfBeFc0jnP2MqOZCp
-puRGPBlO2nxUCvoGcPRUKGQK9XSYmxcmaSFyzKVDMLnmH+Lakj5vaY9a8ZAcZTz7
-T5qePxKAxg+RIlH8Ftc485QP3fhqPYPrRsL3g6peiqCvIRshoP1MSoh19boI+1uX
-wHQ/NyDkL5ErKC5JCSpaeF8VG1ek570kKWQLuQAbnlXZw+Sqfu35CIdizHaYGEcx
-xA8oXH4L2JaT2x9GKDSpCmB2xXy/NVamUg==
------END CERTIFICATE-----
diff --git a/src/leap/testing/https_server.py b/src/leap/testing/https_server.py
deleted file mode 100644
index 21191c32..00000000
--- a/src/leap/testing/https_server.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from BaseHTTPServer import HTTPServer
-import os
-import ssl
-import SocketServer
-import threading
-import unittest
-
-_where = os.path.split(__file__)[0]
-
-
-def where(filename):
- return os.path.join(_where, filename)
-
-
-class HTTPSServer(HTTPServer):
- def server_bind(self):
- SocketServer.TCPServer.server_bind(self)
- self.socket = ssl.wrap_socket(
- self.socket, server_side=True,
- certfile=where("leaptestscert.pem"),
- keyfile=where("leaptestskey.pem"),
- ca_certs=where("cacert.pem"),
- ssl_version=ssl.PROTOCOL_SSLv23)
-
-
-class TestServerThread(threading.Thread):
- def __init__(self, test_object, request_handler):
- threading.Thread.__init__(self)
- self.request_handler = request_handler
- self.test_object = test_object
-
- def run(self):
- self.server = HTTPSServer(('localhost', 0), self.request_handler)
- host, port = self.server.socket.getsockname()
- self.test_object.HOST, self.test_object.PORT = host, port
- self.test_object.server_started.set()
- self.test_object = None
- try:
- self.server.serve_forever(0.05)
- finally:
- self.server.server_close()
-
- def stop(self):
- self.server.shutdown()
-
-
-class BaseHTTPSServerTestCase(unittest.TestCase):
- """
- derived classes need to implement a request_handler
- """
- def setUp(self):
- self.server_started = threading.Event()
- self.thread = TestServerThread(self, self.request_handler)
- self.thread.start()
- self.server_started.wait()
-
- def tearDown(self):
- self.thread.stop()
-
- def get_server(self):
- host, port = self.HOST, self.PORT
- if host == "127.0.0.1":
- host = "localhost"
- return "%s:%s" % (host, port)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/testing/leaptestscert.pem b/src/leap/testing/leaptestscert.pem
deleted file mode 100644
index 65596b1a..00000000
--- a/src/leap/testing/leaptestscert.pem
+++ /dev/null
@@ -1,84 +0,0 @@
-Certificate:
- Data:
- Version: 3 (0x2)
- Serial Number:
- eb:f4:05:2d:3d:0f:c6:f3
- Signature Algorithm: sha1WithRSAEncryption
- Issuer: C=US, ST=cyberspace, O=LEAP Encryption Access Project, CN=tests-leap.se/emailAddress=info@leap.se
- Validity
- Not Before: Aug 31 16:30:17 2012 GMT
- Not After : Aug 31 16:30:17 2013 GMT
- Subject: C=US, ST=cyberspace, L=net, O=LEAP Encryption Access Project, CN=localhost/emailAddress=info@leap.se
- Subject Public Key Info:
- Public Key Algorithm: rsaEncryption
- Public-Key: (2048 bit)
- Modulus:
- 00:bc:f1:c4:05:ce:4b:d5:9b:9a:fa:c1:a5:0c:89:
- 15:7e:05:69:b6:a4:62:38:3a:d6:14:4a:36:aa:3c:
- 31:70:54:2e:bf:7d:05:19:ad:7b:0c:a9:a6:7d:46:
- be:83:62:cb:ea:b9:48:6c:7d:78:a0:10:0b:ad:8a:
- 74:7a:b8:ff:32:85:64:36:90:dc:38:dd:90:6e:07:
- 82:70:ae:5f:4e:1f:f4:46:98:f3:98:b4:fa:08:65:
- bf:d6:ec:a9:ba:7e:a8:f0:40:a2:d0:1a:cb:e6:fc:
- 95:c5:54:63:92:5b:b8:0a:36:cc:26:d3:2b:ad:16:
- ff:49:53:f4:65:7c:64:27:9a:f5:12:75:11:a5:0c:
- 5a:ea:1e:e4:31:f3:a6:2b:db:0e:4a:5d:aa:47:3a:
- f0:5e:2a:d5:6f:74:b6:f8:bc:9a:73:d0:fa:8a:be:
- a8:69:47:9b:07:45:d9:b5:cd:1c:9b:c5:41:9a:65:
- cc:99:a0:bd:bf:b5:e8:9f:66:5f:69:c9:6d:c8:68:
- 50:68:74:ae:8e:12:7e:9c:24:4f:dc:05:61:b7:8a:
- 6d:2a:95:43:d9:3f:fe:d8:c9:a7:ae:63:cd:30:d5:
- 95:84:18:2d:12:b5:2d:a6:fe:37:dd:74:b8:f8:a5:
- 59:18:8f:ca:f7:ae:63:0d:9d:66:51:7d:9c:40:48:
- 9b:a1
- Exponent: 65537 (0x10001)
- X509v3 extensions:
- X509v3 Basic Constraints:
- CA:FALSE
- Netscape Comment:
- OpenSSL Generated Certificate
- X509v3 Subject Key Identifier:
- B2:50:B4:C6:38:8F:BA:C4:3B:69:4C:6B:45:7C:CF:08:48:36:02:E0
- X509v3 Authority Key Identifier:
- keyid:9E:92:44:BF:81:67:96:A2:9B:7E:6E:E6:A6:BB:9D:29:DA:E7:38:0A
-
- Signature Algorithm: sha1WithRSAEncryption
- aa:ab:d4:27:e3:cb:42:05:55:fd:24:b3:e5:55:7d:fb:ce:6c:
- ff:c7:96:f0:7d:30:a1:53:4a:04:eb:a4:24:5e:96:ee:65:ef:
- e5:aa:08:47:9d:aa:95:2a:bb:6a:28:9f:51:62:63:d9:7d:1a:
- 81:a0:72:f7:9f:33:6b:3b:f4:dc:85:cd:2a:ee:83:a9:93:3d:
- 75:53:91:fa:0b:1b:10:83:11:2c:03:4e:ac:bf:c3:e6:25:74:
- 9f:14:13:4a:43:66:c2:d7:1c:6c:94:3e:a6:f3:a5:bd:01:2c:
- 9f:20:29:2e:62:82:12:d8:8b:70:1b:88:2b:18:68:5a:45:80:
- 46:2a:6a:d5:df:1f:d3:e8:57:39:0a:be:1a:d8:b0:3e:e5:b6:
- c3:69:b7:5e:c0:7b:b3:a8:a6:78:ee:0a:3d:a0:74:40:fb:42:
- 9f:f4:98:7f:47:cc:15:28:eb:b1:95:77:82:a8:65:9b:46:c3:
- 4f:f9:f4:72:be:bd:24:28:5c:0d:b3:89:e4:13:71:c8:a7:54:
- 1b:26:15:f3:c1:b2:a9:13:77:54:c2:b9:b0:c7:24:39:00:4c:
- 1a:a7:9b:e7:ad:4a:3a:32:c2:81:0d:13:2d:27:ea:98:00:a9:
- 0e:9e:38:3b:8f:80:34:17:17:3d:49:7e:f4:a5:19:05:28:08:
- 7d:de:d3:1f
------BEGIN CERTIFICATE-----
-MIIECjCCAvKgAwIBAgIJAOv0BS09D8bzMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD
-VQQGEwJVUzETMBEGA1UECAwKY3liZXJzcGFjZTEnMCUGA1UECgweTEVBUCBFbmNy
-eXB0aW9uIEFjY2VzcyBQcm9qZWN0MRYwFAYDVQQDDA10ZXN0cy1sZWFwLnNlMRsw
-GQYJKoZIhvcNAQkBFgxpbmZvQGxlYXAuc2UwHhcNMTIwODMxMTYzMDE3WhcNMTMw
-ODMxMTYzMDE3WjCBijELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmN5YmVyc3BhY2Ux
-DDAKBgNVBAcMA25ldDEnMCUGA1UECgweTEVBUCBFbmNyeXB0aW9uIEFjY2VzcyBQ
-cm9qZWN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGluZm9A
-bGVhcC5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALzxxAXOS9Wb
-mvrBpQyJFX4FabakYjg61hRKNqo8MXBULr99BRmtewyppn1GvoNiy+q5SGx9eKAQ
-C62KdHq4/zKFZDaQ3DjdkG4HgnCuX04f9EaY85i0+ghlv9bsqbp+qPBAotAay+b8
-lcVUY5JbuAo2zCbTK60W/0lT9GV8ZCea9RJ1EaUMWuoe5DHzpivbDkpdqkc68F4q
-1W90tvi8mnPQ+oq+qGlHmwdF2bXNHJvFQZplzJmgvb+16J9mX2nJbchoUGh0ro4S
-fpwkT9wFYbeKbSqVQ9k//tjJp65jzTDVlYQYLRK1Lab+N910uPilWRiPyveuYw2d
-ZlF9nEBIm6ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
-blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFLJQtMY4j7rEO2lM
-a0V8zwhINgLgMB8GA1UdIwQYMBaAFJ6SRL+BZ5aim35u5qa7nSna5zgKMA0GCSqG
-SIb3DQEBBQUAA4IBAQCqq9Qn48tCBVX9JLPlVX37zmz/x5bwfTChU0oE66QkXpbu
-Ze/lqghHnaqVKrtqKJ9RYmPZfRqBoHL3nzNrO/Tchc0q7oOpkz11U5H6CxsQgxEs
-A06sv8PmJXSfFBNKQ2bC1xxslD6m86W9ASyfICkuYoIS2ItwG4grGGhaRYBGKmrV
-3x/T6Fc5Cr4a2LA+5bbDabdewHuzqKZ47go9oHRA+0Kf9Jh/R8wVKOuxlXeCqGWb
-RsNP+fRyvr0kKFwNs4nkE3HIp1QbJhXzwbKpE3dUwrmwxyQ5AEwap5vnrUo6MsKB
-DRMtJ+qYAKkOnjg7j4A0Fxc9SX70pRkFKAh93tMf
------END CERTIFICATE-----
diff --git a/src/leap/testing/leaptestskey.pem b/src/leap/testing/leaptestskey.pem
deleted file mode 100644
index fe6291a1..00000000
--- a/src/leap/testing/leaptestskey.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEAvPHEBc5L1Zua+sGlDIkVfgVptqRiODrWFEo2qjwxcFQuv30F
-Ga17DKmmfUa+g2LL6rlIbH14oBALrYp0erj/MoVkNpDcON2QbgeCcK5fTh/0Rpjz
-mLT6CGW/1uypun6o8ECi0BrL5vyVxVRjklu4CjbMJtMrrRb/SVP0ZXxkJ5r1EnUR
-pQxa6h7kMfOmK9sOSl2qRzrwXirVb3S2+Lyac9D6ir6oaUebB0XZtc0cm8VBmmXM
-maC9v7Xon2ZfacltyGhQaHSujhJ+nCRP3AVht4ptKpVD2T/+2MmnrmPNMNWVhBgt
-ErUtpv433XS4+KVZGI/K965jDZ1mUX2cQEiboQIDAQABAoIBAQCh/+yhSbrtoCgm
-PegEsnix/3QfPBxWt+Obq/HozglZlWQrnMbFuF+bgM4V9ZUdU5UhYNF+66mEG53X
-orGyE3IDYCmHO3cGbroKDPhDIs7mTjGEYlniIbGLh6oPXgU8uKKis9ik84TGPOUx
-NuTUtT07zLYHx+FX3DLwLUKLzTaWWSRgA7nxNwCY8aPqDxCkXEyZHvSlm9KYZnhe
-nVevycoHR+chxL6X/ebbBt2FKR7tl4328mlDXvMXr0vahPH94CuXEvfTj+f6ZxZF
-OctdikyRfd8O3ebrUw0XjafPYyTsDMH0/rQovEBVlecEHqh6Z9dBFlogRq5DSun9
-jem4bBXRAoGBAPGPi4g21pTQPqTFxpqea8TsPqIfo3csfMDPdzT246MxzALHqCfG
-yZi4g2JYJrReSWHulZDORO5skSKNEb5VTA/3xFhKLt8CULZOakKBDLkzRXlnDFXg
-Jsu9vtjDWjQcJsdsRx1tc5V6s+hmel70aaUu/maUlEYZnyIXaTe+1SB1AoGBAMg9
-EMEO5YN52pOI5qPH8j7uyVKtZWKRiR6jb5KA5TxWqZalSdPV6YwDqV/e+HjWrZNw
-kSEFONY0seKpIHwXchx91aym7rDHUgOoBQfCWufRMYvRXLhfOTBu4X+U52++i8wt
-FvKgh6eSmc7VayAaDfHp7yfrIfS03IiN0T35mGj9AoGAPCoXg7a83VW8tId5/trE
-VsjMlM6yhSU0cUV7GFsBuYzWlj6qODX/0iTqvFzeTwBI4LZu1CE78/Jgd62RJMnT
-5wo8Ag1//RVziuSe/K9tvtbxT9qFrQHmR8qbtRt65Q257uOeFstDBZEJLDIR+oJ/
-qZ+5x0zsXUVWaERSdYr3RF0CgYEApKDgN3oB5Ti4Jnh1984aMver+heptYKmU9RX
-lQH4dsVhpQO8UTgcTgtso+/0JZWLHB9+ksFyW1rzrcETfjLglOA4XzzYHeuiWHM5
-v4lhqBpsO+Ij80oHAPUI3RYVud/VnEauCUlGftWfM1hwPPJu6KhHAnDleAWDE5pV
-oDinwBkCgYEAnn/OceaqA2fNYp1IRegbFzpewjUlHLq3bXiCIVhO7W/HqsdfUxjE
-VVdjEno/pAG7ZCO5j8u+rLkG2ZIVY3qsUENUiXz52Q08qEltgM8nfirK7vIQkfd9
-YISRE3QHYJd+ArY4v+7rNeF1O5eIEyzPAbvG5raeZFcZ6POxy66uWKo=
------END RSA PRIVATE KEY-----
diff --git a/src/leap/testing/pyqt.py b/src/leap/testing/pyqt.py
deleted file mode 100644
index 6edaf059..00000000
--- a/src/leap/testing/pyqt.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from PyQt4 import QtCore
-
-_oldConnect = QtCore.QObject.connect
-_oldDisconnect = QtCore.QObject.disconnect
-_oldEmit = QtCore.QObject.emit
-
-
-def _wrapConnect(callableObject):
- """
- Returns a wrapped call to the old version of QtCore.QObject.connect
- """
- @staticmethod
- def call(*args):
- callableObject(*args)
- _oldConnect(*args)
- return call
-
-
-def _wrapDisconnect(callableObject):
- """
- Returns a wrapped call to the old version of QtCore.QObject.disconnect
- """
- @staticmethod
- def call(*args):
- callableObject(*args)
- _oldDisconnect(*args)
- return call
-
-
-def enableSignalDebugging(**kwargs):
- """
- Call this to enable Qt Signal debugging. This will trap all
- connect, and disconnect calls.
- """
-
- f = lambda *args: None
- connectCall = kwargs.get('connectCall', f)
- disconnectCall = kwargs.get('disconnectCall', f)
- emitCall = kwargs.get('emitCall', f)
-
- def printIt(msg):
- def call(*args):
- print msg, args
- return call
- QtCore.QObject.connect = _wrapConnect(connectCall)
- QtCore.QObject.disconnect = _wrapDisconnect(disconnectCall)
-
- def new_emit(self, *args):
- emitCall(self, *args)
- _oldEmit(self, *args)
-
- QtCore.QObject.emit = new_emit
diff --git a/src/leap/testing/qunittest.py b/src/leap/testing/qunittest.py
deleted file mode 100644
index b89ccec3..00000000
--- a/src/leap/testing/qunittest.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# **qunittest** is an standard Python `unittest` enhancement for PyQt4,
-# allowing
-# you to test asynchronous code using standard synchronous testing facility.
-#
-# The source for `qunittest` is available on [GitHub][gh], and released under
-# the MIT license.
-#
-# Slightly modified by The Leap Project.
-
-### Prerequisites
-
-# Import unittest2 or unittest
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-# ... and some standard Python libraries
-import sys
-import functools
-import contextlib
-import re
-
-# ... and several PyQt classes
-from PyQt4.QtCore import QTimer
-from PyQt4.QtTest import QTest
-from PyQt4 import QtGui
-
-### The code
-
-
-# Override standard main method, by invoking it inside PyQt event loop
-
-def main(*args, **kwargs):
- qapplication = QtGui.QApplication(sys.argv)
-
- QTimer.singleShot(0, unittest.main(*args, **kwargs))
- qapplication.exec_()
-
-"""
-This main substitute does not integrate with unittest.
-
-Note about mixing the event loop and unittests:
-
-Unittest will fail if we keep more than one reference to a QApplication.
-(pyqt expects to be and only one).
-So, for the things that need a QApplication to exist, do something like:
-
- self.app = QApplication()
- QtGui.qApp = self.app
-
-in the class setUp, and::
-
- QtGui.qApp = None
- self.app = None
-
-in the class tearDown.
-
-For some explanation about this, see
- http://stuvel.eu/blog/127/multiple-instances-of-qapplication-in-one-process
-and
- http://www.riverbankcomputing.com/pipermail/pyqt/2010-September/027705.html
-"""
-
-
-# Helper returning the name of a given signal
-
-def _signal_name(signal):
- s = repr(signal)
- name_re = "signal (\w+) of (\w+)"
- match = re.search(name_re, s, re.I)
- if not match:
- return "??"
- return "%s#%s" % (match.group(2), match.group(1))
-
-
-class _SignalConnector(object):
- """ Encapsulates signal assertion testing """
- def __init__(self, test, signal, callable_):
- self.test = test
- self.callable_ = callable_
- self.called_with = None
- self.emited = False
- self.signal = signal
- self._asserted = False
-
- signal.connect(self.on_signal_emited)
-
- # Store given parameters and mark signal as `emited`
- def on_signal_emited(self, *args, **kwargs):
- self.called_with = (args, kwargs)
- self.emited = True
-
- def assertEmission(self):
- # Assert once wheter signal was emited or not
- was_asserted = self._asserted
- self._asserted = True
-
- if not was_asserted:
- if not self.emited:
- self.test.fail(
- "signal %s not emited" % (_signal_name(self.signal)))
-
- # Call given callable is necessary
- if self.callable_:
- args, kwargs = self.called_with
- self.callable_(*args, **kwargs)
-
- def __enter__(self):
- # Assert emission when context is entered
- self.assertEmission()
- return self.called_with
-
- def __exit__(self, *_):
- return False
-
-### Unit Testing
-
-# `qunittest` does not force much abould how test should look - it just adds
-# several helpers for asynchronous code testing.
-#
-# Common test case may look like this:
-#
-# import qunittest
-# from calculator import Calculator
-#
-# class TestCalculator(qunittest.TestCase):
-# def setUp(self):
-# self.calc = Calculator()
-#
-# def test_should_add_two_numbers_synchronously(self):
-# # given
-# a, b = 2, 3
-#
-# # when
-# r = self.calc.add(a, b)
-#
-# # then
-# self.assertEqual(5, r)
-#
-# def test_should_calculate_factorial_in_background(self):
-# # given
-#
-# # when
-# self.calc.factorial(20)
-#
-# # then
-# self.assertEmited(self.calc.done) with (args, kwargs):
-# self.assertEqual([2432902008176640000], args)
-#
-# if __name__ == "__main__":
-# main()
-#
-# Test can be run by typing:
-#
-# python test_calculator.py
-#
-# Automatic test discovery is not supported now, because testing PyQt needs
-# an instance of `QApplication` and its `exec_` method is blocking.
-#
-
-
-### TestCase class
-
-class TestCase(unittest.TestCase):
- """
- Extends standard `unittest.TestCase` with several PyQt4 testing features
- useful for asynchronous testing.
- """
- def __init__(self, *args, **kwargs):
- super(TestCase, self).__init__(*args, **kwargs)
-
- self._clearSignalConnectors()
- self._succeeded = False
- self.addCleanup(self._clearSignalConnectors)
- self.tearDown = self._decorateTearDown(self.tearDown)
-
- ### Protected methods
-
- def _clearSignalConnectors(self):
- self._connectedSignals = []
-
- def _decorateTearDown(self, tearDown):
- @functools.wraps(tearDown)
- def decorator():
- self._ensureEmitedSignals()
- return tearDown()
- return decorator
-
- def _ensureEmitedSignals(self):
- """
- Checks if signals were acually emited. Raises AssertionError if no.
- """
- # TODO: add information about line
- for signal in self._connectedSignals:
- signal.assertEmission()
-
- ### Assertions
-
- def assertEmited(self, signal, callable_=None, timeout=1):
- """
- Asserts if given `signal` was emited. Waits 1 second by default,
- before asserts signal emission.
-
- If `callable_` is given, it should be a function which takes two
- arguments: `args` and `kwargs`. It will be called after blocking
- operation or when assertion about signal emission is made and
- signal was emited.
-
- When timeout is not `False`, method call is blocking, and ends
- after `timeout` seconds. After that time, it validates wether
- signal was emited.
-
- When timeout is `False`, method is non blocking, and test should wait
- for signals afterwards. Otherwise, at the end of the test, all
- signal emissions are checked if appeared.
-
- Function returns context, which yields to list of parameters given
- to signal. It can be useful for testing given parameters. Following
- code:
-
- with self.assertEmited(widget.signal) as (args, kwargs):
- self.assertEqual(1, len(args))
- self.assertEqual("Hello World!", args[0])
-
- will wait 1 second and test for correct parameters, is signal was
- emtied.
-
- Note that code:
-
- with self.assertEmited(widget.signal, timeout=False) as (a, k):
- # Will not be invoked
-
- will always fail since signal cannot be emited in the time of its
- connection - code inside the context will not be invoked at all.
- """
-
- connector = _SignalConnector(self, signal, callable_)
- self._connectedSignals.append(connector)
- if timeout:
- self.waitFor(timeout)
- connector.assertEmission()
-
- return connector
-
- ### Helper methods
-
- @contextlib.contextmanager
- def invokeAfter(self, seconds, callable_=None):
- """
- Waits given amount of time and executes the context.
-
- If `callable_` is given, executes it, instead of context.
- """
- self.waitFor(seconds)
- if callable_:
- callable_()
- else:
- yield
-
- def waitFor(self, seconds):
- """
- Waits given amount of time.
-
- self.widget.loadImage(url)
- self.waitFor(seconds=10)
- """
- QTest.qWait(seconds * 1000)
-
- def succeed(self, bool_=True):
- """ Marks test as suceeded for next `failAfter()` invocation. """
- self._succeeded = self._succeeded or bool_
-
- def failAfter(self, seconds, message=None):
- """
- Waits given amount of time, and fails the test if `succeed(bool)`
- is not called - in most common case, `succeed(bool)` should be called
- asynchronously (in signal handler):
-
- self.widget.signal.connect(lambda: self.succeed())
- self.failAfter(1, "signal not emited?")
-
- After invocation, test is no longer consider as succeeded.
- """
- self.waitFor(seconds)
- if not self._succeeded:
- self.fail(message)
-
- self._succeeded = False
-
-### Credits
-#
-# * **Who is responsible:** [Dawid Fatyga][df]
-# * **Source:** [GitHub][gh]
-# * **Doc. generator:** [rocco][ro]
-#
-# [gh]: https://www.github.com/dejw/qunittest
-# [df]: https://github.com/dejw
-# [ro]: http://rtomayko.github.com/rocco/
-#
diff --git a/src/leap/testing/test_basetest.py b/src/leap/testing/test_basetest.py
deleted file mode 100644
index 14d8f8a3..00000000
--- a/src/leap/testing/test_basetest.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""becase it's oh so meta"""
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest
-
-import os
-import StringIO
-
-from leap.testing.basetest import BaseLeapTest
-
-# global for tempdir checking
-_tempdir = None
-
-
-class _TestCaseRunner(object):
- def run_testcase(self, testcase=None):
- if not testcase:
- return None
- loader = unittest.TestLoader()
- suite = loader.loadTestsFromTestCase(testcase)
-
- # Create runner, and run testcase
- io = StringIO.StringIO()
- runner = unittest.TextTestRunner(stream=io)
- results = runner.run(suite)
- return results
-
-
-class TestAbstractBaseLeapTest(unittest.TestCase, _TestCaseRunner):
-
- def test_abstract_base_class(self):
- class _BaseTest(BaseLeapTest):
- def test_dummy_method(self):
- pass
-
- def test_tautology(self):
- assert True
-
- results = self.run_testcase(_BaseTest)
-
- # should be 2 errors: NotImplemented
- # raised for setUp/tearDown
- self.assertEquals(results.testsRun, 2)
- self.assertEquals(len(results.failures), 0)
- self.assertEquals(len(results.errors), 2)
-
-
-class TestInitBaseLeapTest(BaseLeapTest):
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_path_is_changed(self):
- os_path = os.environ['PATH']
- self.assertTrue(os_path.startswith(self.tempdir))
-
- def test_old_path_is_saved(self):
- self.assertTrue(len(self.old_path) > 1)
-
-
-class TestCleanedBaseLeapTest(unittest.TestCase, _TestCaseRunner):
-
- def test_tempdir_is_cleaned_after_tests(self):
- class _BaseTest(BaseLeapTest):
- def setUp(self):
- global _tempdir
- _tempdir = self.tempdir
-
- def tearDown(self):
- pass
-
- def test_tempdir_created(self):
- self.assertTrue(os.path.isdir(self.tempdir))
-
- def test_tempdir_created_on_setupclass(self):
- self.assertEqual(_tempdir, self.tempdir)
-
- results = self.run_testcase(_BaseTest)
- self.assertEquals(results.testsRun, 2)
- self.assertEquals(len(results.failures), 0)
- self.assertEquals(len(results.errors), 0)
-
- # did we cleaned the tempdir?
- self.assertFalse(os.path.isdir(_tempdir))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/util/__init__.py b/src/leap/util/__init__.py
index a70a9a8b..e69de29b 100644
--- a/src/leap/util/__init__.py
+++ b/src/leap/util/__init__.py
@@ -1,9 +0,0 @@
-import logging
-logger = logging.getLogger(__name__)
-
-try:
- import pygeoip
- HAS_GEOIP = True
-except ImportError:
- logger.debug('PyGeoIP not found. Disabled Geo support.')
- HAS_GEOIP = False
diff --git a/src/leap/util/certs.py b/src/leap/util/certs.py
deleted file mode 100644
index f0f790e9..00000000
--- a/src/leap/util/certs.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import os
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-def get_mac_cabundle():
- # hackaround bundle error
- # XXX this needs a better fix!
- f = os.path.split(__file__)[0]
- sep = os.path.sep
- f_ = sep.join(f.split(sep)[:-2])
- verify = os.path.join(f_, 'cacert.pem')
- #logger.error('VERIFY PATH = %s' % verify)
- exists = os.path.isfile(verify)
- #logger.error('do exist? %s', exists)
- if exists:
- return verify
diff --git a/src/leap/util/coroutines.py b/src/leap/util/coroutines.py
index 0657fc04..e7ccfacf 100644
--- a/src/leap/util/coroutines.py
+++ b/src/leap/util/coroutines.py
@@ -4,13 +4,10 @@
from __future__ import division, print_function
-import logging
from subprocess import PIPE, Popen
import sys
from threading import Thread
-logger = logging.getLogger(__name__)
-
ON_POSIX = 'posix' in sys.builtin_module_names
@@ -41,7 +38,8 @@ for each event
if callable(callback):
callback(m)
else:
- logger.debug('not a callable passed')
+ #XXX log instead
+ print('not a callable passed')
except GeneratorExit:
return
@@ -74,7 +72,7 @@ def watch_output(out, observers):
:type out: fd
:param observers: tuple of coroutines to send data\
for each event
- :type observers: tuple
+ :type ovservers: tuple
"""
observer_dict = dict(((observer, process_events(observer))
for observer in observers))
diff --git a/src/leap/util/dicts.py b/src/leap/util/dicts.py
deleted file mode 100644
index 001ca96b..00000000
--- a/src/leap/util/dicts.py
+++ /dev/null
@@ -1,268 +0,0 @@
-# Backport of OrderedDict() class that runs
-# on Python 2.4, 2.5, 2.6, 2.7 and pypy.
-# Passes Python2.7's test suite and incorporates all the latest updates.
-
-try:
- from thread import get_ident as _get_ident
-except ImportError:
- from dummy_thread import get_ident as _get_ident
-
-try:
- from _abcoll import KeysView, ValuesView, ItemsView
-except ImportError:
- pass
-
-
-class OrderedDict(dict):
- 'Dictionary that remembers insertion order'
- # An inherited dict maps keys to values.
- # The inherited dict provides __getitem__, __len__, __contains__, and get.
- # The remaining methods are order-aware.
- # Big-O running times for all methods are the same as for regular
- # dictionaries.
-
- # The internal self.__map dictionary maps keys to links in a doubly
- # linked list.
- # The circular doubly linked list starts and ends with a sentinel element.
- # The sentinel element never gets deleted (this simplifies the algorithm).
- # Each link is stored as a list of length three: [PREV, NEXT, KEY].
-
- def __init__(self, *args, **kwds):
- '''Initialize an ordered dictionary. Signature is the same as for
- regular dictionaries, but keyword arguments are not recommended
- because their insertion order is arbitrary.
-
- '''
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__root
- except AttributeError:
- self.__root = root = [] # sentinel node
- root[:] = [root, root, None]
- self.__map = {}
- self.__update(*args, **kwds)
-
- def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
- 'od.__setitem__(i, y) <==> od[i]=y'
- # Setting a new item creates a new link which goes at the end
- # of the linked list, and the inherited dictionary is updated
- # with the new key/value pair.
- if key not in self:
- root = self.__root
- last = root[0]
- last[1] = root[0] = self.__map[key] = [last, root, key]
- dict_setitem(self, key, value)
-
- def __delitem__(self, key, dict_delitem=dict.__delitem__):
- 'od.__delitem__(y) <==> del od[y]'
- # Deleting an existing item uses self.__map to find the link which is
- # then removed by updating the links in the predecessor and successor
- # nodes.
- dict_delitem(self, key)
- link_prev, link_next, key = self.__map.pop(key)
- link_prev[1] = link_next
- link_next[0] = link_prev
-
- def __iter__(self):
- 'od.__iter__() <==> iter(od)'
- root = self.__root
- curr = root[1]
- while curr is not root:
- yield curr[2]
- curr = curr[1]
-
- def __reversed__(self):
- 'od.__reversed__() <==> reversed(od)'
- root = self.__root
- curr = root[0]
- while curr is not root:
- yield curr[2]
- curr = curr[0]
-
- def clear(self):
- 'od.clear() -> None. Remove all items from od.'
- try:
- for node in self.__map.itervalues():
- del node[:]
- root = self.__root
- root[:] = [root, root, None]
- self.__map.clear()
- except AttributeError:
- pass
- dict.clear(self)
-
- def popitem(self, last=True):
- '''od.popitem() -> (k, v), return and remove a (key, value) pair.
- Pairs are returned in LIFO order if last is true or FIFO order if
- false.
- '''
- if not self:
- raise KeyError('dictionary is empty')
- root = self.__root
- if last:
- link = root[0]
- link_prev = link[0]
- link_prev[1] = root
- root[0] = link_prev
- else:
- link = root[1]
- link_next = link[1]
- root[1] = link_next
- link_next[0] = root
- key = link[2]
- del self.__map[key]
- value = dict.pop(self, key)
- return key, value
-
- # -- the following methods do not depend on the internal structure --
-
- def keys(self):
- 'od.keys() -> list of keys in od'
- return list(self)
-
- def values(self):
- 'od.values() -> list of values in od'
- return [self[key] for key in self]
-
- def items(self):
- 'od.items() -> list of (key, value) pairs in od'
- return [(key, self[key]) for key in self]
-
- def iterkeys(self):
- 'od.iterkeys() -> an iterator over the keys in od'
- return iter(self)
-
- def itervalues(self):
- 'od.itervalues -> an iterator over the values in od'
- for k in self:
- yield self[k]
-
- def iteritems(self):
- 'od.iteritems -> an iterator over the (key, value) items in od'
- for k in self:
- yield (k, self[k])
-
- def update(*args, **kwds):
- '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
-
- If E is a dict instance, does: for k in E: od[k] = E[k]
- If E has a .keys() method, does: for k in E.keys():
- od[k] = E[k]
- Or if E is an iterable of items, does: for k, v in E: od[k] = v
- In either case, this is followed by: for k, v in F.items():
- od[k] = v
- '''
-
- if len(args) > 2:
- raise TypeError('update() takes at most 2 positional '
- 'arguments (%d given)' % (len(args),))
- elif not args:
- raise TypeError('update() takes at least 1 argument (0 given)')
- self = args[0]
- # Make progressively weaker assumptions about "other"
- other = ()
- if len(args) == 2:
- other = args[1]
- if isinstance(other, dict):
- for key in other:
- self[key] = other[key]
- elif hasattr(other, 'keys'):
- for key in other.keys():
- self[key] = other[key]
- else:
- for key, value in other:
- self[key] = value
- for key, value in kwds.items():
- self[key] = value
-
- __update = update # let subclasses override update
- # without breaking __init__
-
- __marker = object()
-
- def pop(self, key, default=__marker):
- '''od.pop(k[,d]) -> v
- remove specified key and return the corresponding value.
- If key is not found, d is returned if given,
- otherwise KeyError is raised.
-
- '''
- if key in self:
- result = self[key]
- del self[key]
- return result
- if default is self.__marker:
- raise KeyError(key)
- return default
-
- def setdefault(self, key, default=None):
- 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
- if key in self:
- return self[key]
- self[key] = default
- return default
-
- def __repr__(self, _repr_running={}):
- 'od.__repr__() <==> repr(od)'
- call_key = id(self), _get_ident()
- if call_key in _repr_running:
- return '...'
- _repr_running[call_key] = 1
- try:
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
- finally:
- del _repr_running[call_key]
-
- def __reduce__(self):
- 'Return state information for pickling'
- items = [[k, self[k]] for k in self]
- inst_dict = vars(self).copy()
- for k in vars(OrderedDict()):
- inst_dict.pop(k, None)
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def copy(self):
- 'od.copy() -> a shallow copy of od'
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
- and values equal to v (which defaults to None).
-
- '''
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- '''od.__eq__(y) <==> od==y.
- Comparison to another OD is order-sensitive
- while comparison to a regular mapping is order-insensitive.
- '''
- if isinstance(other, OrderedDict):
- return len(self) == len(other) and self.items() == other.items()
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
-
- # -- the following methods are only used in Python 2.7 --
-
- def viewkeys(self):
- "od.viewkeys() -> a set-like object providing a view on od's keys"
- return KeysView(self)
-
- def viewvalues(self):
- "od.viewvalues() -> an object providing a view on od's values"
- return ValuesView(self)
-
- def viewitems(self):
- "od.viewitems() -> a set-like object providing a view on od's items"
- return ItemsView(self)
diff --git a/src/leap/util/fileutil.py b/src/leap/util/fileutil.py
index 820ffe46..429e4b12 100644
--- a/src/leap/util/fileutil.py
+++ b/src/leap/util/fileutil.py
@@ -21,7 +21,7 @@ def extend_path():
# XXX add mac / win extended search paths?
-def which(program, path=None):
+def which(program):
"""
an implementation of which
that extends the path with
@@ -67,10 +67,8 @@ def which(program, path=None):
else:
# extended iterator
# with extra path
- if path is None:
- path = os.environ['PATH']
extended_path = chain(
- iter_path(path),
+ iter_path(os.environ["PATH"]),
iter_path(extend_path()))
for candidate in extended_path:
if candidate is not None:
@@ -93,11 +91,6 @@ def mkdir_p(path):
raise
-def mkdir_f(path):
- folder, fname = os.path.split(path)
- mkdir_p(folder)
-
-
def check_and_fix_urw_only(_file):
"""
test for 600 mode and try
diff --git a/src/leap/util/geo.py b/src/leap/util/geo.py
deleted file mode 100644
index 54b29596..00000000
--- a/src/leap/util/geo.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-experimental geo support.
-not yet a feature.
-in debian, we rely on the (optional) geoip-database
-"""
-import os
-import platform
-
-from leap.util import HAS_GEOIP
-
-GEOIP = None
-
-if HAS_GEOIP:
- import pygeoip # we know we can :)
-
- GEOIP_PATH = None
-
- if platform.system() == "Linux":
- PATH = "/usr/share/GeoIP/GeoIP.dat"
- if os.path.isfile(PATH):
- GEOIP_PATH = PATH
- GEOIP = pygeoip.GeoIP(GEOIP_PATH, pygeoip.MEMORY_CACHE)
-
-
-def get_country_name(ip):
- if not GEOIP:
- return
- try:
- country = GEOIP.country_name_by_addr(ip)
- except pygeoip.GeoIPError:
- country = None
- return country if country else "-"
diff --git a/src/leap/util/leap_argparse.py b/src/leap/util/leap_argparse.py
index 3412a72c..9c355134 100644
--- a/src/leap/util/leap_argparse.py
+++ b/src/leap/util/leap_argparse.py
@@ -2,43 +2,19 @@ import argparse
def build_parser():
- """
- all the options for the leap arg parser
- Some of these could be switched on only if debug flag is present!
- """
- epilog = "Copyright 2012 The LEAP Encryption Access Project"
+ epilog = "Copyright 2012 The Leap Project"
parser = argparse.ArgumentParser(description="""
-Launches the LEAP Client""", epilog=epilog)
- parser.add_argument('-d', '--debug', action="store_true",
- help=("Launches client in debug mode, writing debug"
- "info to stdout"))
- parser.add_argument('-l', '--logfile', metavar="LOG FILE", nargs='?',
- action="store", dest="log_file",
- #type=argparse.FileType('w'),
- help='optional log file')
- parser.add_argument('--openvpn-verbosity', nargs='?',
- type=int,
- action="store", dest="openvpn_verb",
- help='verbosity level for openvpn logs [1-6]')
-
- # Not in use, we might want to reintroduce them.
- #parser.add_argument('-i', '--no-provider-checks',
- #action="store_true", default=False,
- #help="skips download of provider config files. gets "
- #"config from local files only. Will fail if cannot "
- #"find any")
- #parser.add_argument('-k', '--no-ca-verify',
- #action="store_true", default=False,
- #help="(insecure). Skips verification of the server "
- #"certificate used in TLS handshake.")
- #parser.add_argument('-c', '--config', metavar="CONFIG FILE", nargs='?',
- #action="store", dest="config_file",
- #type=argparse.FileType('r'),
- #help='optional config file')
+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, unknown = parser.parse_known_args()
+ opts = parser.parse_args()
return parser, opts
diff --git a/src/leap/util/misc.py b/src/leap/util/misc.py
deleted file mode 100644
index d869a1ba..00000000
--- a/src/leap/util/misc.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""
-misc utils
-"""
-import psutil
-
-from leap.base.constants import OPENVPN_BIN
-
-
-class ImproperlyConfigured(Exception):
- """
- """
-
-
-def null_check(value, value_name):
- try:
- assert value is not None
- except AssertionError:
- raise ImproperlyConfigured(
- "%s parameter cannot be None" % value_name)
-
-
-def get_openvpn_pids():
- # binary name might change
-
- openvpn_pids = []
- for p in psutil.process_iter():
- try:
- # XXX Not exact!
- # Will give false positives.
- # we should check that cmdline BEGINS
- # with openvpn or with our wrapper
- # (pkexec / osascript / whatever)
- if OPENVPN_BIN in ' '.join(p.cmdline):
- openvpn_pids.append(p.pid)
- except psutil.error.AccessDenied:
- pass
- return openvpn_pids
diff --git a/src/leap/util/tests/test_fileutil.py b/src/leap/util/test_fileutil.py
index f5131b3d..849decaf 100644
--- a/src/leap/util/tests/test_fileutil.py
+++ b/src/leap/util/test_fileutil.py
@@ -52,7 +52,8 @@ class FileUtilTest(unittest.TestCase):
def test_is_user_executable(self):
"""
- touch_exec_file creates in mode 700?
+ test that a 700 file
+ is an 700 file. kindda oximoronic, but...
"""
# XXX could check access X_OK
@@ -62,10 +63,10 @@ class FileUtilTest(unittest.TestCase):
def test_which(self):
"""
- which implementation ok?
not a very reliable test,
but I cannot think of anything smarter now
I guess it's highly improbable that copy
+ command is somewhere else..?
"""
# XXX yep, we can change the syspath
# for the test... !
@@ -77,7 +78,7 @@ class FileUtilTest(unittest.TestCase):
def test_mkdir_p(self):
"""
- our own mkdir -p implementation ok?
+ test our mkdir -p implementation
"""
testdir = self.get_file_path(
os.path.join('test', 'foo', 'bar'))
@@ -87,7 +88,8 @@ class FileUtilTest(unittest.TestCase):
def test_check_and_fix_urw_only(self):
"""
- ensure check_and_fix_urx_only ok?
+ test function that fixes perms on
+ files that should be rw only for owner
"""
fp = self.touch_exec_file()
mode = self.get_mode(fp)
@@ -95,6 +97,3 @@ class FileUtilTest(unittest.TestCase):
fileutil.check_and_fix_urw_only(fp)
mode = self.get_mode(fp)
self.assertEqual(mode, int('600', 8))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/util/tests/test_leap_argparse.py b/src/leap/util/test_leap_argparse.py
index 082919b7..1442e827 100644
--- a/src/leap/util/tests/test_leap_argparse.py
+++ b/src/leap/util/test_leap_argparse.py
@@ -23,13 +23,5 @@ class LeapArgParseTest(unittest.TestCase):
['--debug'])
self.assertEqual(
opts,
- Namespace(
- config_file=None,
- debug=True,
- log_file=None,
- no_provider_checks=False,
- no_ca_verify=False,
- openvpn_verb=None))
-
-if __name__ == "__main__":
- unittest.main()
+ Namespace(config_file=None,
+ debug=True))
diff --git a/src/leap/util/tests/__init__.py b/src/leap/util/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/leap/util/tests/__init__.py
+++ /dev/null
diff --git a/src/leap/util/tests/test_translations.py b/src/leap/util/tests/test_translations.py
deleted file mode 100644
index 794daeba..00000000
--- a/src/leap/util/tests/test_translations.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import unittest
-
-from leap.util import translations
-
-
-class TrasnlationsTestCase(unittest.TestCase):
- """
- tests for translation functions and classes
- """
-
- def setUp(self):
- self.trClass = translations.LEAPTranslatable
-
- def test_trasnlatable(self):
- tr = self.trClass({"en": "house", "es": "casa"})
- eq = self.assertEqual
- eq(tr.tr(to="es"), "casa")
- eq(tr.tr(to="en"), "house")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/leap/util/translations.py b/src/leap/util/translations.py
deleted file mode 100644
index f55c8fba..00000000
--- a/src/leap/util/translations.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import inspect
-import logging
-
-from PyQt4.QtCore import QCoreApplication
-from PyQt4.QtCore import QLocale
-
-logger = logging.getLogger(__name__)
-
-"""
-here I could not do all that I wanted.
-the context is not getting passed to the xml file.
-Looks like pylupdate4 is somehow a hack that does not
-parse too well the python ast.
-I guess we could generate the xml for ourselves as a last recourse.
-"""
-
-# XXX BIG NOTE:
-# RESIST the temptation to get the translate function
-# more compact, or have the Context argument passed as a variable
-# Its name HAS to be explicit due to how the pylupdate parser
-# works.
-
-
-qtTranslate = QCoreApplication.translate
-
-
-def translate(*args, **kwargs):
- """
- our magic function.
- translate(Context, text, comment)
- """
- if len(args) == 1:
- obj = args[0]
- if isinstance(obj, LEAPTranslatable) and hasattr(obj, 'tr'):
- return obj.tr()
-
- klsname = None
- try:
- # get class value from instance
- # using live object inspection
- prev_frame = inspect.stack()[1][0]
- locals_ = inspect.getargvalues(prev_frame).locals
- self = locals_.get('self')
- if self:
-
- # Trying to get the class name
- # but this is useless, the parser
- # has already got the context.
- klsname = self.__class__.__name__
- #print 'KLSNAME -- ', klsname
- except:
- logger.error('error getting stack frame')
-
- if klsname and len(args) == 1:
- nargs = (klsname,) + args
- return qtTranslate(*nargs)
-
- else:
- return qtTranslate(*args)
-
-
-class LEAPTranslatable(dict):
- """
- An extended dict that implements a .tr method
- so it can be translated on the fly by our
- magic translate method
- """
-
- try:
- locale = str(QLocale.system().name()).split('_')[0]
- except:
- logger.warning("could not get system locale!")
- print "could not get system locale!"
- locale = "en"
-
- def tr(self, to=None):
- if not to:
- to = self.locale
- _tr = self.get(to, None)
- if not _tr:
- _tr = self.get("en", None)
- return _tr
diff --git a/src/leap/util/web.py b/src/leap/util/web.py
deleted file mode 100644
index 15de0561..00000000
--- a/src/leap/util/web.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
-web related utilities
-"""
-
-
-class UsageError(Exception):
- """ """
-
-
-def get_https_domain_and_port(full_domain):
- """
- returns a tuple with domain and port
- from a full_domain string that can
- contain a colon
- """
- full_domain = unicode(full_domain)
- if full_domain is None:
- return None, None
-
- https_sch = "https://"
- http_sch = "http://"
-
- if full_domain.startswith(https_sch):
- full_domain = full_domain.lstrip(https_sch)
- elif full_domain.startswith(http_sch):
- raise UsageError(
- "cannot be called with a domain "
- "that begins with 'http://'")
-
- domain_split = full_domain.split(':')
- _len = len(domain_split)
- if _len == 1:
- domain, port = full_domain, 443
- elif _len == 2:
- domain, port = domain_split
- else:
- raise UsageError(
- "must be called with one only parameter"
- "in the form domain[:port]")
- return domain, port
diff --git a/tests/test_qt_environment.py b/tests/test_qt_environment.py
index e90d527f..08fccf4b 100644
--- a/tests/test_qt_environment.py
+++ b/tests/test_qt_environment.py
@@ -2,7 +2,7 @@ import sys
import unittest
import sip
-#sip.setapi('QVariant', 2)
+sip.setapi('QVariant', 2)
from PyQt4 import QtGui
@@ -37,7 +37,3 @@ class QtEnvironTest(unittest.TestCase):
self.assertEqual(
self.win.trayIcon.isSystemTrayAvailable(),
True)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index a9107c71..00000000
--- a/tox.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[tox]
-envlist = py26,py27
-
-[testenv]
-deps = -r{toxinidir}/pkg/requirements.pip
- -r{toxinidir}/pkg/test-requirements.pip
-sitepackages = True
-commands = xvfb-run nosetests leap --exclude-dir=src/leap/soledad
-
-[testenv:pep8]
-deps = pep8==1.1
-commands = pep8 --repeat --show-source src/leap setup.py --ignore=E202,W602 --exclude=*_rc.py --repeat