diff options
author | Ivan Alejandro <ivanalejandro0@gmail.com> | 2013-09-20 17:28:25 -0300 |
---|---|---|
committer | Ivan Alejandro <ivanalejandro0@gmail.com> | 2013-09-20 17:28:25 -0300 |
commit | 1f0f8efc4cb985c082b3b8fe7b3dc45aed047a47 (patch) | |
tree | 8d96c7a374c91699c55fb8c5b609aac222a94e74 | |
parent | 9568093138c85212e15d50ade5d5fc7dcec9ff6e (diff) | |
parent | 222ce2a1513a3776b3277ded365672d7d43ad2e4 (diff) |
Merge branch 'release-0.3.3'
Conflicts:
pkg/requirements.pip
setup.py
src/leap/bitmask/config/leapsettings.py
46 files changed, 1525 insertions, 785 deletions
@@ -1,4 +1,34 @@ -0.3.2 Sep 9: +0.3.3 Sep 20 -- "the calm after the tempest" release: + o Remove execution bits in text files in bundle. Closes #3617. + o Use generic bad username/password message instead of specific ones when + the user uses incorrect data during login. Closes #3656. + o Fix LoggerWindow saving more than one line return per line in the logs + file. Closes #3714. + o Fix keyring imports so we do not get import errors. Closes: #3759 + o Catch logout problem, display a user message and allow log back in after a + successful logout if there was a logout error before. Closes #3774. + o Fix path prefix helper for the bundle and add regresion tests. Closes #3778. + o Prevent dialogs closing the app when it has been minimized to the tray. Closes #3791. + o Do not try to install resolv-update globally. Closes: #3803 + o Inconsistent hide/show main window from tray action. Closes #3821. + o Allow SMTP to start even when provider does not offer EIP. Closes: #3847 + o Fix username case problem at register/login. Closes #3857. + o Catch IndexError on `first` utility. + o Update git repo name in docs. Closes: #3417 + o Move STANDALONE flag to a module and unify get_path_prefix queries. + Closes #3636. + o Display the Encrypted Internet and Encrypted Email status in the systray + tooltip. Closes #3758. + o Tasktray menu changes, closes #3792. + - Remove the provider domain item (e.g. bitmask.net). + - Rename the EIP status menu items to be more descriptive. + - Change the EIP status menu items from disabled menu items + to submenus with children. + - Move the EIP action menu items under the EIP status submenu tree. + o Adds --version flag. Closes: #3816 + o Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 + +0.3.2 Sep 6 -- the "no crashes or anything" release: o Fix up script in non-bundle linuces. Closes: #3450 o Logout stops imap and smtp services. Closes: #3553 o Properly daemonize polkit-gnome-authentication-agent. Closes: #3554 diff --git a/MANIFEST.in b/MANIFEST.in index 926e1793..876393da 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,3 +9,6 @@ include README.rst include CHANGELOG include src/leap/bitmask/util/reqs.txt include src/leap/bitmask/crypto/tests/wrongcert.pem + +include src/leap/bitmask/gui/ui_*.py +include src/leap/bitmask/gui/*_rc.py @@ -34,8 +34,8 @@ Bitmask depends on these libraries: Python packages are listed in ``pkg/requirements.pip`` and ``pkg/test-requirements.pip`` -Debian -^^^^^^ +Getting dependencies under debian +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ With a Debian based system, to be able to run Bitmask you need to run the following command:: @@ -47,7 +47,7 @@ Installing After getting the source and installing all the dependencies, proceed to install ``bitmask`` package:: $ make - $ sudo LEAP_VENV_SKIP_PYSIDE=1 python setup.py install + $ sudo python2 setup.py install Running ------- @@ -60,7 +60,7 @@ If you are testing a new provider and do not have a CA certificate chain tied to $ bitmask --danger -But **DO NOT use it on a regular bases**. +But **DO NOT use it on a regular basis**. **WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. @@ -69,9 +69,13 @@ But **DO NOT use it on a regular bases**. Hacking ======= -The Bitmask git repository is available at:: +Get the source from the main Bitmask repo:: - git://leap.se/leap_client + git clone https://leap.se/git/bitmask_client + +The code is also browsable online at:: + + https://leap.se/git/?p=bitmask_client.git Some steps need to be run when setting a development environment for the first time. @@ -90,14 +94,14 @@ Symlink your global pyside libraries:: And make your working tree available to your pythonpath:: - (bitmask)$ python setup.py develop + (bitmask)$ python2 setup.py develop Run Bitmask:: - (bitmask)$ python src/leap/app.py -d + (bitmask)$ bitmask --debug -If you are testing a new provider that doesn't have the proper certificates yet, you can use --danger flag, but **DO NOT use it on a regular bases**. +If you are testing a new provider that doesn't have the proper certificates yet, you can use --danger flag, but **DO NOT use it on a regular basis**. **WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. @@ -117,7 +121,7 @@ which the first time should automagically install all the needed dependencies in License ======= -.. image:: https://raw.github.com/leapcode/leap_client/develop/docs/user/gpl.png +.. image:: https://raw.github.com/leapcode/bitmask_client/develop/docs/user/gpl.png Bitmask is released under the terms of the `GNU GPL version 3`_ or later. diff --git a/changes/bug-3825-include-resources b/changes/bug-3825-include-resources new file mode 100644 index 00000000..083fd05d --- /dev/null +++ b/changes/bug-3825-include-resources @@ -0,0 +1 @@ + o Include resource files and ui in the distrubution tarball. Closes: #3825 diff --git a/data/resources/mainwindow.qrc b/data/resources/mainwindow.qrc index d1268186..1e4159b8 100644 --- a/data/resources/mainwindow.qrc +++ b/data/resources/mainwindow.qrc @@ -2,10 +2,8 @@ <qresource prefix="/"> <file>../images/mask-launcher.png</file> <file>../images/mask-icon.png</file> - <file>../images/watermark.png</file> <file>../images/leap-gray-big.png</file> <file>../images/Blue-Arrow-Right-32.png</file> - <file>../images/Globe.png</file> <file>../images/leap-color-big.png</file> <file>../images/Arrow-Down-32.png</file> <file>../images/Arrow-Up-32.png</file> @@ -15,7 +13,6 @@ <file>../images/conn_connecting-light.png</file> <file>../images/conn_connected-light.png</file> <file>../images/conn_error-light.png</file> - <file>../images/leap-color-small.png</file> <file>../images/Dialog-accept.png</file> <file>../images/Dialog-error.png</file> <file>../images/Emblem-question.png</file> diff --git a/docs/dev/environment.rst b/docs/dev/environment.rst index 7ce536f9..e942b1cb 100644 --- a/docs/dev/environment.rst +++ b/docs/dev/environment.rst @@ -13,9 +13,11 @@ Cloning the repo :: - git clone git://leap.se/bitmask + git clone git://leap.se/bitmask_client git checkout develop +.. XXX change this when repo changes. + Base Dependencies ------------------ Bitmask depends on these libraries: @@ -121,6 +123,10 @@ If you *only* are running bitmask from inside a virtualenv, you will need to cop Missing Authentication agent ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you are running a desktop other than gnome or unity, you might get an error saying that you are not running the authentication agent. You can launch it like this:: +If you are using linux and running a desktop other than unity or gnome, you might get an error saying that you are not running the authentication agent. For systems with gnome libraries installed you can launch it like this:: /usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1 & + +or if you are a kde user:: + + /usr/lib/kde4/libexec/polkit-kde-authentication-agent-1 & diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index 7a1d2ae1..ed4f7133 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -38,6 +38,8 @@ general options **-s, --standalone** Makes Bitmask use standalone directories for configuration and binary searching. +**-V, --version** Displays Bitmask version and exits. + openvpn options --------------- diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index e6467048..19a19289 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -2,12 +2,12 @@ * [ ] Check that all tests are passing! * [ ] Tag everything * Should be done for the following packages, in order: - 1. leap.common - 2. leap.keymanager - 3. leap.soledad - 4. leap.mail - 5. leap.bitmask - 6. leap.mx + * [ ] 1. leap.common + * [ ] 2. leap.keymanager + * [ ] 3. leap.soledad + * [ ] 4. leap.mail + * [ ] 5. leap.bitmask + * [ ] 6. leap.mx * NOTE: It's assumed that origin is the leap.se repo * [ ] git fetch origin * [ ] git tag -l, and see the latest tagged version (unless it's not a minor version bump, in which case, just bump to it) @@ -17,17 +17,17 @@ - Helper bash line: for i in $(ls changes); do cat changes/$i; echo; done * [ ] Update relnotes.txt if needed. * [ ] git rm changes/* - * [ ] git commit -av * [ ] Review pkg/requirements.pip for everything and update if needed (that's why the order). - See whatever has been introduced in changes/VERSION_COMPAT - Reset changes/VERSION_COMPAT - * [ ] git tag -s X.Y.Z (note the -s so that it's a signed tag) The message should be something like: Tag <package> version X.Y.Z + * [ ] git commit -av # we should add a commit message here... + * [ ] git checkout master && git pull origin master && git merge --no-ff release-X.Y.Z && git push origin master + * [ ] git tag -s X.Y.Z -m "Tag <package> version X.Y.Z" # (note the -s so that it's a signed tag and -m to specify the message for the tag) * [ ] git push origin X.Y.Z - * [ ] git checkout master && git pull origin master && git merge release-X.Y.Z && git push origin master - * [ ] git checkout develop && git pull origin develop && git merge release-X.Y.Z && git push origin develop + * [ ] git checkout develop && git merge master && git push origin develop * [ ] Build and upload bundles * [ ] Use the scripts under pkg/<os>/ to build the the bundles. - * [ ] Sign them with gpg -a <path/to/bundle> + * [ ] Sign them with gpg -a --sign --detach-sign <path/to/bundle> * [ ] Upload bundle and signature to web-uploads@salmon.leap.se:~/public/client/<os>/Bitmask-<os>-<ver>.(tar.bz2,dmg,zip) * [ ] Update symbolic link for latest upload and signature: * [ ] ~/public/client/Bitmask-<os>-latest @@ -38,3 +38,5 @@ Notes ----- (*) this checklist kindly borrowed from tahoe-lafs documentation =) + +For a good reference look at http://nvie.com/posts/a-successful-git-branching-model/ diff --git a/docs/testers/howto.rst b/docs/testers/howto.rst index 9c6561ed..61d38787 100644 --- a/docs/testers/howto.rst +++ b/docs/testers/howto.rst @@ -25,8 +25,9 @@ Install dependencies ^^^^^^^^^^^^^^^^^^^^ First, install all the base dependencies plus git, virtualenv and development files needed to compile several extensions:: - apt-get install openvpn git-core python-dev python-qt4 python-setuptools python-virtualenv + apt-get install openvpn git-core python-dev python-pyside python-setuptools python-virtualenv +.. TODO Should review these dependencies. ^^ Bootstrap script ^^^^^^^^^^^^^^^^ @@ -41,12 +42,12 @@ Download and source the following script in the parent folder where you want you .. code-block:: bash cd /tmp - wget https://raw.github.com/leapcode/leap_client/develop/pkg/scripts/bitmask_bootstrap.sh + wget https://raw.github.com/leapcode/bitmask_client/develop/pkg/scripts/bitmask_bootstrap.sh source bitmask_bootstrap.sh Tada! If everything went well, you should be able to run bitmask by typing:: - bin/bitmask + bitmask Noticed that your prompt changed? That was *virtualenv*. Keep reading... diff --git a/docs/user/install.rst b/docs/user/install.rst index 81807a43..e5765302 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -88,15 +88,13 @@ Installing Bitmask is as simple as using `pip <http://www.pip-installer.org/>`_ Show me the code! ----------------- -.. XXX UPDATE REPO NAMES AS SOON AS #3417 is DONE - You can get the code from LEAP public git repository :: - $ git clone git://leap.se/leap_client + $ git clone git://leap.se/bitmask_client Or from the github mirror :: - $ git clone git://github.com/leapcode/leap_client.git + $ git clone git://github.com/leapcode/bitmask_client.git Once you have grabbed a copy of the sources, you can install it into your site-packages easily :: diff --git a/pkg/linux/build_bundle.sh b/pkg/linux/build_bundle.sh index e6a1043f..520ff256 100755 --- a/pkg/linux/build_bundle.sh +++ b/pkg/linux/build_bundle.sh @@ -1,98 +1,106 @@ -REPOS_ROOT=$1 -VERSION=$2 -TEMPLATE_BUNDLE=$3 -JOINT_CHANGELOG=$4 -DEST=$5 +#!/bin/bash +# +# USAGE NOTES: +# +# This script is meant to be used as follows: +# user@host ~ $ ./build_bundle.sh ~/tmp 0.3.2 ~/tmp/0.3.1/Bitmask-linux64-0.3.1/ /media/Shared/CHANGELOG ~/tmp/bundle_out/ +# +# So we would have: +# REPOS_ROOT -> ~/tmp +# VERSION -> 0.3.2 +# TEMPLATE_BUNDLE -> ~/tmp/0.3.1/Bitmask-linux64-0.3.1/ +# JOINT_CHANGELOG -> /media/Shared/CHANGELOG +# DEST -> ~/tmp/bundle_out/ +# +# We need to set different PATHs in order to use a specific version of PySide, +# supposing that we have our compiled pyside in '~/pyside/sandbox', the above command would be: +# user@host ~ $ PYTHONPATH=~/pyside/sandbox/lib/python2.7/site-packages/ LD_LIBRARY_PATH=~/pyside/sandbox/lib/ PATH=$PATH:~/pyside/sandbox/bin/ ./build_bundle.sh ~/tmp 0.3.2 ~/tmp/0.3.1/Bitmask-linux64-0.3.1/ /media/sf_Shared/CHANGELOG ~/tmp/bundle_out/ + + +# Required arguments +REPOS_ROOT=$1 # Root path for all the needed repositories +VERSION=$2 # Version number that we are building +TEMPLATE_BUNDLE=$3 # A template used to create the new bundle +JOINT_CHANGELOG=$4 # Joint changelog for all the repositories +DEST=$5 # Destination folder for the bundle + +# Helper variables +REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail" +ARCH=$(uname -m | sed 's/x86_//;s/i[3-6]86/32/') -# clean template +# Bundle structure +LEAP_LIB=$TEMPLATE_BUNDLE/lib/leap/ +BITMASK_BIN=$TEMPLATE_BUNDLE/bitmask +BUNDLE_NAME=Bitmask-linux$ARCH-$VERSION +# clean template rm $TEMPLATE_BUNDLE/CHANGELOG rm $TEMPLATE_BUNDLE/relnotes.txt rm -rf $TEMPLATE_BUNDLE/apps/leap rm -rf $TEMPLATE_BUNDLE/lib/leap/{common,keymanager,soledad,mail} -# checkout VERSION in all repos - -for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail} - do - cd $REPOS_ROOT/$i +# checkout the latest tag in all repos +for repo in $REPOSITORIES; do + cd $REPOS_ROOT/$repo git fetch - git checkout $VERSION - done - -# make ui in client + # checkout to the latest annotated tag, supress 'detached head' warning + git checkout --quiet `git describe --abbrev=0` +done -cd $REPOS_ROOT/leap_client +# make: compile ui and resources in client +cd $REPOS_ROOT/bitmask_client make -# cp client - -cp -r $REPOS_ROOT/leap_client/src/leap $TEMPLATE_BUNDLE/apps/leap +# copy the latest client code to the template +cp -r $REPOS_ROOT/bitmask_client/src/leap $TEMPLATE_BUNDLE/apps/leap # setup sdist client - -cd $REPOS_ROOT/leap_client +cd $REPOS_ROOT/bitmask_client python setup.py sdist # extract $VERSION and copy _version.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py - +# copy _version.py (versioneer) and reqs.txt (requirements) to the bundle template cd dist rm -rf leap.bitmask-$VERSION tar xzf leap.bitmask-$VERSION.tar.gz cp leap.bitmask-$VERSION/src/leap/bitmask/_version.py $TEMPLATE_BUNDLE/apps/leap/bitmask/_version.py cp leap.bitmask-$VERSION/src/leap/bitmask/util/reqs.txt $TEMPLATE_BUNDLE/apps/leap/bitmask/util/reqs.txt -# cp common, soledad(client and common), mail and keymanager in TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/ - -LEAP_LIB=$TEMPLATE_BUNDLE/lib/leap/ - +# add the other needed projects to $LEAP_LIB +# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/ cp -r $REPOS_ROOT/leap_pycommon/src/leap/common $LEAP_LIB cp -r $REPOS_ROOT/soledad/common/src/leap/soledad $LEAP_LIB cp -r $REPOS_ROOT/soledad/client/src/leap/soledad/client $LEAP_LIB/soledad cp -r $REPOS_ROOT/leap_mail/src/leap/mail $LEAP_LIB cp -r $REPOS_ROOT/keymanager/src/leap/keymanager $LEAP_LIB -# cp leap_client launcher to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/Bitmask - -BITMASK_BIN=$TEMPLATE_BUNDLE/bitmask - -cd $REPOS_ROOT/leap_client_launcher/build/ +# copy bitmask launcher to the bundle template +# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/Bitmask +cd $REPOS_ROOT/bitmask_launcher/build/ make cp src/launcher $BITMASK_BIN -# cp launcher.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/ - -cd $REPOS_ROOT/leap_client_launcher/src/ +# copy launcher.py to template bundle +# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/ +cd $REPOS_ROOT/bitmask_client_launcher/src/ cp launcher.py $TEMPLATE_BUNDLE/apps/ -# cp relnotes to TEMPLATE_BUNDLE - -cp $REPOS_ROOT/leap_client/relnotes.txt $TEMPLATE_BUNDLE - -# cp joint_chglog to TEMPLATE_BUNDLE - +# copy relnotes, joint changelog and LICENSE to TEMPLATE_BUNDLE +cp $REPOS_ROOT/bitmask_client/relnotes.txt $TEMPLATE_BUNDLE cp $JOINT_CHANGELOG $TEMPLATE_BUNDLE/CHANGELOG +cp $REPOS_ROOT/bitmask_client/LICENSE $TEMPLATE_BUNDLE/LICENSE -# cp LICENSE to TEMPLATE_BUNDLE - -cp $REPOS_ROOT/leap_client/LICENSE $TEMPLATE_BUNDLE/LICENSE - -# clean pyc$ - +# clean *.pyc files cd $TEMPLATE_BUNDLE -for i in $(find . | grep pyc$); - do - rm $i - done +find . -name "*.pyc" -delete -# create tarball +# remove execution flags (because vbox fs) and set read permissions for all +chmod 644 CHANGELOG LICENSE README -ARCH=$(uname -m | sed 's/x86_//;s/i[3-6]86/32/') -BUNDLE_NAME=Bitmask-linux$ARCH-$VERSION +# create tarball TMP=/tmp/$BUNDLE_NAME -rm -rf $TMP -mkdir -p $TMP +rm -rf $TMP && mkdir -p $TMP # clean temp dir cp -R $TEMPLATE_BUNDLE/* $TMP cd /tmp tar cjf $DEST/$BUNDLE_NAME.tar.bz2 $BUNDLE_NAME @@ -100,8 +108,7 @@ cd rm -rf $TMP # go back to develop in all repos -for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail} - do - cd $REPOS_ROOT/$i +for repo in $REPOSITORIES; do + cd $REPOS_ROOT/$repo git checkout develop - done +done diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 081f3ba9..154e51b4 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -16,11 +16,10 @@ twisted qt4reactor python-gnupg python-daemon # this should not be needed for Windows. - -keyring<=2.9 # See #3759 +keyring leap.common>=0.3.2 -leap.soledad.client>=0.3.0 +leap.soledad.client>=0.4.0 leap.keymanager>=0.2.0 leap.mail>=0.3.2 diff --git a/pkg/scripts/bitmask_bootstrap.sh b/pkg/scripts/bitmask_bootstrap.sh index 42eb0af9..bd568ebd 100755 --- a/pkg/scripts/bitmask_bootstrap.sh +++ b/pkg/scripts/bitmask_bootstrap.sh @@ -33,7 +33,7 @@ echo "${cc_green}Installing bitmask...${cc_normal}" # change "develop" for any other branch you want. -pip install -e 'git://leap.se/leap_client@develop#egg=leap.bitmask' +pip install -e 'git+https://leap.se/git/bitmask_client@develop#egg=leap.bitmask' cd bitmask-testbuild diff --git a/relnotes.txt b/relnotes.txt index 349b430c..19cfb879 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,101 +1,104 @@ -ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.2 +ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.3 The LEAP team is pleased to announce the immediate availability of -version 0.3.2 of Bitmask, the Internet Encryption Toolkit. +version 0.3.3 of Bitmask, the Internet Encryption Toolkit, codename +"the calm after the tempest". https://downloads.leap.se/client/ -LEAP (LEAP Encryption Access Project) develops a plan to secure -everyday communication, breaking down into discrete services. +LEAP (LEAP Encryption Access Project) develops a plan to secure everyday +communication, breaking down into discrete services. -Bitmask is the desktop client to connect to the services offered -by the LEAP Platform. In the current phase the supported services are -Encrypted Internet Proxy and Encrypted Mail. +Bitmask is the desktop client to connect to the services offered by the +LEAP Platform. In the current phase the supported services are Encrypted +Internet Proxy and Encrypted Mail. -The Encrypted Internet Proxy provides circumvention, location anonymization, -and traffic encryption in a hassle-free, automatically self-configuring -fashion. +The Encrypted Internet Proxy provides circumvention, location +anonymization, and traffic encryption in a hassle-free, automatically +self-configuring fashion. -Encrypted Mail offers automatic encryption and decryption for both outgoing -and incoming email, adding public key cryptography to your mail without you -ever having to worry about key distribution or signature verification. +Encrypted Mail offers automatic encryption and decryption for both +outgoing and incoming email, adding public key cryptography to your mail +without you ever having to worry about key distribution or signature +verification. -You can read about this and many other cool things in the user manual and the -developer notes, which can be found online at: +You can read about this and many other cool things in the user manual +and the developer notes, which can be found online at: http://bitmask.rtfd.org/ -WARNING: This is still part of a beta release of our software, a lot of testing and -auditing is still needed, so indeed use it, and feed us back, fork it and contribute -to its development, but by any means DO NOT trust your life to it (yet!). +WARNING: This is still part of a beta release of our software, a lot of +testing and auditing is still needed, so indeed use it, and feed us back, +fork it and contribute to its development, but by any means DO NOT trust +your life to it (yet!). WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.3.2 is mostly a bugfix release, with some minor improvements. Mail -service is a bit more polished, and we are slowly making our potential packagers -happy. Refer to the CHANGELOG for the funny details. +Bitmask 0.3.3 is mostly a bugfix release, with some minor improvements. +On this release, we have fixed many UI bugs, and have undergone internal +reorganizations in the code. This release also bumps the requirement +for Soledad, the encrypted data syncronization engine behind Bitmask, +which has experienced a backward-incompatible change. You can refer to +the CHANGELOG for the meat. -You can connect to the Encrypted Internet Proxy service offered by a provider of -your choice, and enjoy a encrypted internet connection that the spying eyes can only -track back to your provider. +As always, you can connect to the Encrypted Internet Proxy service offered +by a provider of your choice, and enjoy a encrypted internet connection +that the spying eyes can only track back to your provider. -The Encrypted Mail services will run local SMTP and IMAP proxies that, once you -configure the mail client of your choice, will automatically encrypt and decrypt -your email using GPG encryption under the hood. +The Encrypted Mail services will run local SMTP and IMAP proxies that, +once you configure the mail client of your choice, will automatically +encrypt and decrypt your email using GPG encryption under the hood. -The first run wizard will help you registering an user with your selected -provider, downloading all the config files needed to connect to the various LEAP -services. +If it is the first time you run Bitmask, the first run wizard will help +you registering an user with your selected provider, downloading all +the config files needed to connect to the various LEAP services. LICENSE -You may use Bitmask under the GNU General Public License, -version 3 or, at your option, any later version. See the file -"COPYING.GPL" for the terms of the GNU General Public -License, version 3. +You may use Bitmask under the GNU General Public License, version 3 or, +at your option, any later version. See the file "LICENSE" for the terms +of the GNU General Public License, version 3. -In addition, as a special exception, the copyright holders give -permission to link the code of portions of this program with the -OpenSSL library under certain conditions as described in each -individual source file, and distribute linked combinations -including the two. +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library +under certain conditions as described in each individual source file, +and distribute linked combinations including the two. INSTALLATION -We distribute the current version of Bitmask as standalone bundles -for GNU/Linux and OSX, but it is likely that you are able to run it under -other systems, specially if you are skillful and patience is one of your -virtues. +We distribute the current version of Bitmask as standalone bundles for +GNU/Linux and OSX, but it is likely that you are able to run it under +other systems, specially if you are skillful and patience is one of +your virtues. Have a look at "docs/user/install.rst". -Packages will be soon provided for debian and ubuntu, and the release of -windows bundles will be resumed shortly. +Packages will be soon provided for debian and ubuntu, and the release +of windows bundles will be resumed shortly. -We will love to hear if you are interested in help making packages available for -any other system. +We will love to hear if you are interested in help making packages +available for any other system. BUGS -You can send the bugs our way by pointing your telnet session to port 443 on -https://leap.se/code. We will do our best to make them follow our -intensive bug-reeducation program. +You can send the bugs our way by pointing your telnet session to port +443 on https://leap.se/code. We will do our best to make them follow +our intensive bug-reeducation program. HACKING You can find us in the #leap-dev channel on the freenode network. -If you are lucky enough, you can also spot us drinking mate, sleepless in -night trains, rooftops, rainforests, lonely islands and, always, beyond -any border. +If you are lucky enough, you can also spot us drinking mate, sleepless +in night trains, rooftops, rainforests, lonely islands and, always, +beyond any border. The LEAP team, -Sep 06, 2013 -Somewhere in the middle of the intertubes. +Sep 20, 2013 Somewhere in the middle of the intertubes. @@ -18,10 +18,10 @@ """ Setup file for bitmask. """ - from __future__ import print_function import sys +import re if not sys.version_info[0] == 2: print("[ERROR] Sorry, Python 3 is not supported (yet). " @@ -65,10 +65,13 @@ trove_classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", - "Topic :: Communications", "Topic :: Security", - "Topic :: System :: Networking", - "Topic :: Utilities" + 'Topic :: Security :: Cryptography', + "Topic :: Communications", + 'Topic :: Communications :: Email', + 'Topic :: Communications :: Email :: Post-Office :: IMAP', + 'Topic :: Internet', + "Topic :: Utilities", ] @@ -139,7 +142,7 @@ data_files = [] if IS_LINUX: # XXX use check_for_permissions to install data - # globally. See #3805 + # globally. Or make specific install command. See #3805 data_files = [ ("share/polkit-1/actions", ["pkg/linux/polkit/net.openvpn.gui.leap.policy"]), @@ -147,23 +150,42 @@ if IS_LINUX: ["pkg/linux/resolv-update"]), ] +DOWNLOAD_BASE = ('https://github.com/leapcode/bitmask_client/' + 'archive/%s.tar.gz') +VERSION = versioneer.get_version() +DOWNLOAD_URL = "" + +# get the short version for the download url +short_ver = re.findall('\d+\.\d+\.\d+', VERSION) +if len(short_ver) > 0: + DOWNLOAD_URL = DOWNLOAD_BASE % short_ver[0] + + setup( name="leap.bitmask", package_dir={"": "src"}, - version=versioneer.get_version(), + version=VERSION, cmdclass=cmdclass, - description="The Internet Encryption Toolkit", + description=("The Internet Encryption Toolkit: " + "Encrypted Internet Proxy and Encrypted Mail."), + # XXX unify the long_description on a file of its own, so we + # can reuse it easily. long_description=( - "Desktop Client for the LEAP Platform." + "Bitmask is the multiplatform desktop client for the LEAP Platform." "\n" - "LEAP (LEAP Encryption Access Project) develops " - "a multi-year plan to secure everyday communication, breaking down" - "into discrete services, to be rolled out one at a time.\n" - "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." + "The LEAP Encryption Access Project develops " + "a multi-year plan to secure everyday communication.\n " + "The Encrypted Internet Proxy (EIP) provides circumvention, location " + "anonymization, and traffic encryption in a hassle-free, " + "automatically self-configuring fashion.\n" + "Encrypted Mail offers automatic encryption and decryption for " + "both outgoing and incoming email, adding public key cryptography " + "to your mail without you ever having to worry about key distribution " + "or signature verification. \n" + "The Encrypted Mail services will run local SMTP and IMAP proxies " + "that, once you configure the mail client of your choice, will " + "automatically encrypt and decrypt your email using GPG encryption " + "under the hood." ), classifiers=trove_classifiers, install_requires=parsed_reqs, @@ -171,10 +193,13 @@ setup( tests_require=utils.parse_requirements( reqfiles=['pkg/requirements-testing.pip']), keywords=('Bitmask, LEAP, client, qt, encryption, ' - 'proxy, openvpn, imap, smtp'), + 'proxy, openvpn, imap, smtp, gnupg'), author='The LEAP Encryption Access Project', author_email='info@leap.se', - url='https://leap.se', + maintainer='Kali Kaneko', + maintainer_email='kali@leap.se', + url='https://bitmask.rtfd.org', + download_url=DOWNLOAD_URL, license='GPL-3+', packages=find_packages( 'src', diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index ebdd53c4..a4642e27 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -71,5 +71,5 @@ except ImportError: #the setup.py setver pass -__short_version__ = first(re.findall('\d\.\d\.\d', __version__)) +__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__)) __full_version__ = __appname__ + '/' + str(__version__) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 158f1afe..02b1693d 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -14,6 +14,31 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# M:::::::MMMMMMMMMM~:::::::::::::::::::::::::::::::::::::~MMMMMMMMMM~:::::::M +# M:::::MMM$$$$$77$MMMMN~:::::::::::::::::::::::::::::~NMMMM$77$$$$$MMM::::::M +# M:::~MMZ$$$$$777777I8MMMM~:::::::::::::::::::::::~MMMMDI777777$$$$$$MM:::::M +# M:::MMZ$$$$$777777IIIIIZMMMM:::::::::::::::::::MMNMZIIIII777777$$$$$$MM::::M +# M::DMN$$$$$777777IIIIIII??7DDNM+:::::::::::=MDDD7???IIIIII777777$$$$$DMN:::M +# M::MM$$$$$7777777IIIIIII????+?88OOMMMMMMMOO88???????IIIIIII777777$$$$$MM:::M +# M::MM$$$$$777777IIIIIIII??????++++7ZZ$$ZI+++++??????IIIIIIII777777$$$$MM~::M +# M:~MM$$$$77777Z8OIIIIIIII??????++++++++++++++??????IIIIIIIO8Z77777$$$$NM+::M +# M::MM$$$777MMMMMMMMMMMZ?II???????+++++++++???????III$MMMMMMMMMMM7777$$DM$::M +# M:~MM$$77MMMI~::::::$MMMM$?I????????????????????I$MMMMZ~::::::+MMM77$$MM~::M +# M::MM$7777MM::::::::::::MMMMI?????????????????IMMMM:::::::::::~MM7777$MM:::M +# M::MM777777MM~:::::::::::::MMMD?I?????????IIDMMM,:::::::::::::MM777777MM:::M +# M::DMD7777IIMM$::::::::::::?MMM?I??????????IMMM$::::::::::::7MM7I77778MN:::M +# M:::MM777IIIIMMMN~:::::::MMMM?II???+++++????IIMMMM::::::::MMMMIIII777MM::::M +# M:::ZMM7IIIIIIIOMMMMMMMMMMZ?III???++++++++??III?$MMMMMMMMMMO?IIIIII7MMO::::M +# M::::MMDIIIIIIIIII?IIIII?IIIII???+++===++++??IIIIIIII?II?IIIIIIIIII7MM:::::M +# M:::::MM7IIIIIIIIIIIIIIIIIIIII??+++IZ$$I+++??IIIIIIIIIIIIIIIIIIIII7MM::::::M +# M::::::MMOIIIIIIIIIIIIIIIIIIII?D888MMMMM8O8D?IIIIIIIIIIIIIIIIIIII$MM:::::::M +# M:::::::MMM?IIIIIIIIIIIIIIII7MNMD:::::::::OMNM$IIIIIIIIIIIIIIII?MMM::::::::M +# M::::::::NMMI?IIIIIIIIIII?OMMM:::::::::::::::MMMO?IIIIIIIIIIIIIMMN:::::::::M +# M::::::::::MMMIIIIIIIII?8MMM:::::::::::::::::::MMM8IIIIIIIIIIMMM:::::::::::M +# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M +# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M +# (thanks to: http://www.glassgiant.com/ascii/) import logging import signal @@ -24,6 +49,7 @@ from functools import partial from PySide import QtCore, QtGui +from leap.bitmask import __version__ as VERSION from leap.bitmask.util import leap_argparse from leap.bitmask.util import log_silencer from leap.bitmask.util.leap_log_handler import LeapLogHandler @@ -52,7 +78,7 @@ def install_qtreactor(logger): logger.debug("Qt4 reactor installed") -def add_logger_handlers(debug=False, logfile=None, standalone=False): +def add_logger_handlers(debug=False, logfile=None): """ Create the logger and attach the handlers. @@ -81,7 +107,7 @@ def add_logger_handlers(debug=False, logfile=None, standalone=False): console.setLevel(level) console.setFormatter(formatter) - silencer = log_silencer.SelectiveSilencerFilter(standalone=standalone) + silencer = log_silencer.SelectiveSilencerFilter() console.addFilter(silencer) logger.addHandler(console) logger.debug('Console handler plugged!') @@ -133,6 +159,11 @@ def main(): print "Could not ensure server: %r" % (e,) _, opts = leap_argparse.init_leapc_args() + + if opts.version: + print "Bitmask version: %s" % (VERSION,) + sys.exit(0) + standalone = opts.standalone bypass_checks = getattr(opts, 'danger', False) debug = opts.debug @@ -143,12 +174,10 @@ def main(): # Given how paths and bundling works, we need to delay the imports # of certain parts that depend on this path settings. # So first we set all the places where standalone might be queried. - from leap.bitmask.config.providerconfig import ProviderConfig + from leap.bitmask.config import flags from leap.common.config.baseconfig import BaseConfig - from leap.bitmask.services.eip.eipconfig import EIPConfig + flags.STANDALONE = standalone BaseConfig.standalone = standalone - ProviderConfig.standalone = standalone - EIPConfig.standalone = standalone # And then we import all the other stuff from leap.bitmask.gui import locale_rc @@ -156,13 +185,12 @@ def main(): from leap.bitmask.gui.mainwindow import MainWindow from leap.bitmask.platform_init import IS_MAC from leap.bitmask.platform_init.locks import we_are_the_one_and_only - from leap.bitmask import __version__ as VERSION from leap.bitmask.util.requirement_checker import check_requirements # pylint: avoid unused import assert(locale_rc) - logger = add_logger_handlers(debug, logfile, standalone) + logger = add_logger_handlers(debug, logfile) replace_stdout_stderr_with_logging(logger) if not we_are_the_one_and_only(): @@ -180,9 +208,6 @@ def main(): logger.info('Starting app') - ProviderConfig.standalone = standalone - EIPConfig.standalone = standalone - # We force the style if on KDE so that it doesn't load all the kde # libs, which causes a compatibility issue in some systems. # For more info, see issue #3194 @@ -223,7 +248,6 @@ def main(): window = MainWindow( lambda: twisted_main.quit(app), - standalone=standalone, openvpn_verb=openvpn_verb, bypass_checks=bypass_checks) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py new file mode 100644 index 00000000..98395def --- /dev/null +++ b/src/leap/bitmask/config/flags.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# flags.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +This file is meant to be used to store global flags that affect the +application. + +WARNING: You should NOT use this kind of flags unless you're sure of what + you're doing, and someone else tells you that you're right. + Most of the times there is a better and safer alternative. +""" + +# The STANDALONE flag is used to: +# - define a different set of messages for the application when is running +# inside of a bundle or installed system wide. +# - use a relative or system wide path to find the configuration files. +# - search for binaries inside the bundled app instead of the system ones. +# e.g.: openvpn, gpg +STANDALONE = False diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 7d8b5977..338fa475 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -24,7 +24,7 @@ import logging from PySide import QtCore from leap.common.check import leap_assert, leap_assert_type -from leap.common.config import get_path_prefix +from leap.bitmask.util import get_path_prefix logger = logging.getLogger(__name__) @@ -71,15 +71,8 @@ class LeapSettings(object): # values GATEWAY_AUTOMATIC = "Automatic" - def __init__(self, standalone=False): - """ - Constructor - - :param standalone: parameter used to define the location of the config. - :type standalone: bool - """ - self._path_prefix = get_path_prefix(standalone=standalone) - settings_path = os.path.join(self._path_prefix, + def __init__(self): + settings_path = os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) self._settings = QtCore.QSettings(settings_path, @@ -132,7 +125,7 @@ class LeapSettings(object): # other things, not just the directories providers = [] try: - providers_path = os.path.join(self._path_prefix, + providers_path = os.path.join(get_path_prefix(), "leap", "providers") providers = os.listdir(providers_path) except Exception as e: diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index a7808399..c8c8a59e 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -21,10 +21,11 @@ Provider configuration import logging import os -from leap.bitmask.config.provider_spec import leap_provider_spec from leap.common.check import leap_check from leap.common.config.baseconfig import BaseConfig, LocalizedKey +from leap.bitmask.config.provider_spec import leap_provider_spec from leap.bitmask.services import get_service_display_name +from leap.bitmask.util import get_path_prefix logger = logging.getLogger(__name__) @@ -151,13 +152,9 @@ class ProviderConfig(BaseConfig): :type about_to_download: bool """ - cert_path = os.path.join(self.get_path_prefix(), - "leap", - "providers", + cert_path = os.path.join(get_path_prefix(), "leap", "providers", self.get_domain(), - "keys", - "ca", - "cacert.pem") + "keys", "ca", "cacert.pem") if not about_to_download: cert_exists = os.path.exists(cert_path) diff --git a/src/leap/bitmask/crypto/certs.py b/src/leap/bitmask/crypto/certs.py new file mode 100644 index 00000000..244decfd --- /dev/null +++ b/src/leap/bitmask/crypto/certs.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# certs.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Utilities for dealing with client certs +""" +import logging +import os + +from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.common.files import check_and_fix_urw_only +from leap.common.files import mkdir_p + +from leap.common import certs as leap_certs + +logger = logging.getLogger(__name__) + + +def download_client_cert(provider_config, path, session): + """ + Downloads the client certificate for each service. + + :param provider_config: instance of a ProviderConfig + :type provider_config: ProviderConfig + :param path: the path to download the cert to. + :type path: str + :param session: a fetcher.session instance. For the moment we only + support requests.sessions + :type session: requests.sessions.Session + """ + # TODO we should implement the @with_srp_auth decorator + # again. + srp_auth = SRPAuth(provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + cert_uri = "%s/%s/cert" % ( + provider_config.get_api_uri(), + provider_config.get_api_version()) + logger.debug('getting cert from uri: %s' % cert_uri) + + res = session.get(cert_uri, + verify=provider_config + .get_ca_cert_path(), + cookies=cookies, + timeout=REQUEST_TIMEOUT) + res.raise_for_status() + client_cert = res.content + + if not leap_certs.is_valid_pemfile(client_cert): + # XXX raise more specific exception. + raise Exception("The downloaded certificate is not a " + "valid PEM file") + + mkdir_p(os.path.dirname(path)) + + try: + with open(path, "w") as f: + f.write(client_cert) + except IOError as exc: + logger.error( + "Error saving client cert: %r" % (exc,)) + raise + + check_and_fix_urw_only(path) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 41ce130a..bf85f75c 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -171,6 +171,9 @@ class SRPAuth(QtCore.QObject): self._srp_user = None self._srp_a = None + # Error msg displayed if the username or the password is invalid + self._WRONG_USER_PASS = self.tr("Invalid username or password.") + # User credentials stored for password changing checks self._username = None self._password = None @@ -200,8 +203,6 @@ class SRPAuth(QtCore.QObject): """ logger.debug("Authentication preprocessing...") - username = username.lower() - self._srp_user = self._srp.User(username, password, self._hashfun, @@ -265,7 +266,7 @@ class SRPAuth(QtCore.QObject): "Status code = %r. Content: %r" % (init_session.status_code, content)) if init_session.status_code == 422: - raise SRPAuthUnknownUser(self.tr("Unknown user")) + raise SRPAuthUnknownUser(self._WRONG_USER_PASS) raise SRPAuthBadStatusCode(self.tr("There was a problem with" " authentication")) @@ -354,7 +355,7 @@ class SRPAuth(QtCore.QObject): "received: %s", (content,)) logger.error("[%s] Wrong password (HAMK): [%s]" % (auth_result.status_code, error)) - raise SRPAuthBadPassword(self.tr("Wrong password")) + raise SRPAuthBadPassword(self._WRONG_USER_PASS) if auth_result.status_code not in (200,): logger.error("No valid response (HAMK): " @@ -506,7 +507,7 @@ class SRPAuth(QtCore.QObject): leap_assert(self.get_session_id() is None, "Already logged in") # User credentials stored for password changing checks - self._username = username.lower() + self._username = username self._password = password d = threads.deferToThread(self._authentication_preprocessing, @@ -553,6 +554,7 @@ class SRPAuth(QtCore.QObject): except Exception as e: logger.warning("Something went wrong with the logout: %r" % (e,)) + raise else: self.set_session_id(None) self.set_uid(None) @@ -614,7 +616,7 @@ class SRPAuth(QtCore.QObject): :param password: password for this user :type password: str """ - + username = username.lower() d = self.__instance.authenticate(username, password) d.addCallback(self._gui_notify) d.addErrback(self._errback) diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index ece4cad6..ad2ceded 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -91,7 +91,6 @@ class LoggerWindow(QtGui.QDialog): } level = log[LeapLogHandler.RECORD_KEY].levelno message = log[LeapLogHandler.MESSAGE_KEY] - message = message.replace('\n', '<br>\n') if self._logs_to_display[level]: open_tag = "<tr style='" + html_style[level] + "'>" @@ -152,8 +151,13 @@ class LoggerWindow(QtGui.QDialog): if fileName: try: with open(fileName, 'w') as output: - output.write(self.ui.txtLogHistory.toPlainText()) - output.write('\n') + history = self.ui.txtLogHistory.toPlainText() + # Chop some \n. + # html->plain adds several \n because the html is made + # using table cells. + history = history.replace('\n\n\n', '\n') + + output.write(history) logger.debug('Log saved in %s' % (fileName, )) except IOError, e: logger.error("Error saving log file: %r" % (e, )) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 0950462b..200d68aa 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,23 +19,23 @@ Main window for Bitmask. """ import logging import os -import platform -import tempfile -from functools import partial import keyring from PySide import QtCore, QtGui from twisted.internet import threads +from leap.bitmask import __version__ as VERSION from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.gui.loggerwindow import LoggerWindow -from leap.bitmask.gui.preferenceswindow import PreferencesWindow -from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.login import LoginWidget +from leap.bitmask.gui.preferenceswindow import PreferencesWindow +from leap.bitmask.gui import statemachines from leap.bitmask.gui.statuspanel import StatusPanelWidget +from leap.bitmask.gui.wizard import Wizard + from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper @@ -47,6 +47,8 @@ from leap.bitmask.services.mail import imap from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.bitmask.platform_init.initializers import init_platform +from leap.bitmask.services.eip import get_openvpn_management +from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.services.eip.vpnprocess import VPN from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning @@ -58,7 +60,6 @@ from leap.bitmask.services.eip.vpnlaunchers import \ EIPNoPolkitAuthAgentAvailable from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded -from leap.bitmask import __version__ as VERSION from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.leap_log_handler import LeapLogHandler @@ -107,7 +108,6 @@ class MainWindow(QtGui.QMainWindow): user_stopped_eip = False def __init__(self, quit_callback, - standalone=False, openvpn_verb=1, bypass_checks=False): """ @@ -117,10 +117,6 @@ class MainWindow(QtGui.QMainWindow): the application. :type quit_callback: callable - :param standalone: Set to true if the app should use configs - inside its pwd - :type standalone: bool - :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap @@ -147,7 +143,7 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) - self._settings = LeapSettings(standalone) + self._settings = LeapSettings() self._login_widget = LoginWidget( self._settings, @@ -171,12 +167,17 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self._status_panel.start_eip.connect(self._start_eip) - self._status_panel.stop_eip.connect(self._stop_eip) + self._eip_connection = EIPConnection() + + self._eip_connection.qtsigs.connecting_signal.connect( + self._start_eip) + self._eip_connection.qtsigs.disconnecting_signal.connect( + self._stop_eip) + self._status_panel.eip_connection_connected.connect( + self._on_eip_connected) # This is loaded only once, there's a bug when doing that more # than once - self._standalone = standalone self._provider_config = ProviderConfig() # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() @@ -211,12 +212,20 @@ class MainWindow(QtGui.QMainWindow): # This thread is similar to the provider bootstrapper self._eip_bootstrapper = EIPBootstrapper() + # EIP signals ---- move to eip conductor. # TODO change the name of "download_config" signal to # something less confusing (config_ready maybe) self._eip_bootstrapper.download_config.connect( self._eip_intermediate_stage) self._eip_bootstrapper.download_client_certificate.connect( self._finish_eip_bootstrap) + self._vpn = VPN(openvpn_verb=openvpn_verb) + self._vpn.qtsigs.state_changed.connect( + self._status_panel.update_vpn_state) + self._vpn.qtsigs.status_changed.connect( + self._status_panel.update_vpn_status) + self._vpn.qtsigs.process_finished.connect( + self._eip_finished) self._soledad_bootstrapper = SoledadBootstrapper() self._soledad_bootstrapper.download_config.connect( @@ -230,14 +239,6 @@ class MainWindow(QtGui.QMainWindow): self._smtp_bootstrapper.download_config.connect( self._smtp_bootstrapped_stage) - self._vpn = VPN(openvpn_verb=openvpn_verb) - self._vpn.qtsigs.state_changed.connect( - self._status_panel.update_vpn_state) - self._vpn.qtsigs.status_changed.connect( - self._status_panel.update_vpn_status) - self._vpn.qtsigs.process_finished.connect( - self._eip_finished) - self.ui.action_log_out.setEnabled(False) self.ui.action_log_out.triggered.connect(self._logout) self.ui.action_about_leap.triggered.connect(self._about) @@ -251,30 +252,12 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - self._action_eip_provider = QtGui.QAction( - self.tr("No default provider"), self) - self._action_eip_provider.setEnabled(False) - - self._action_eip_status = QtGui.QAction( - self.tr("Encrypted internet is OFF"), - self) - self._action_eip_status.setEnabled(False) - self._status_panel.set_action_eip_status( - self._action_eip_status) - - self._action_mail_status = QtGui.QAction( - self.tr("Encrypted Mail is OFF"), self) + self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) self._action_mail_status.setEnabled(False) - self._status_panel.set_action_mail_status( - self._action_mail_status) + self._status_panel.set_action_mail_status(self._action_mail_status) - self._action_eip_startstop = QtGui.QAction( - self.tr("Turn OFF"), self) - self._action_eip_startstop.triggered.connect( - self._stop_eip) - self._action_eip_startstop.setEnabled(False) - self._status_panel.set_action_eip_startstop( - self._action_eip_startstop) + self._action_eip_startstop = QtGui.QAction("", self) + self._status_panel.set_action_eip_startstop(self._action_eip_startstop) self._action_preferences = QtGui.QAction(self.tr("Preferences"), self) self._action_preferences.triggered.connect(self._show_preferences) @@ -322,8 +305,7 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(standalone=standalone, - bypass_checks=bypass_checks) + self._wizard = Wizard(bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) @@ -331,6 +313,17 @@ class MainWindow(QtGui.QMainWindow): else: self._finish_init() + # Eip machine is a public attribute where the state machine for + # the eip connection will be available to the different components. + # Remember that this will not live in the +1600LOC mainwindow for + # all the eternity, so at some point we will be moving this to + # the EIPConductor or some other clever component that we will + # instantiate from here. + self.eip_machine = None + + # start event machines + self.start_eip_machine() + def _rejected_wizard(self): """ SLOT @@ -428,8 +421,7 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - preferences_window = PreferencesWindow( - self, self._srp_auth, self._settings, self._standalone) + preferences_window = PreferencesWindow(self, self._srp_auth) if self._soledad_ready: preferences_window.set_soledad_ready(self._soledad) @@ -594,8 +586,6 @@ class MainWindow(QtGui.QMainWindow): "no default provider configured") return - self._action_eip_provider.setText(default_provider) - self._enabled_services = self._settings.get_enabled_services( default_provider) @@ -604,6 +594,10 @@ class MainWindow(QtGui.QMainWindow): "providers", default_provider, "provider.json")): + # XXX I think we should not try to re-download config every time, + # it adds some delay. + # Maybe if it's the first run in a session, + # or we can try only if it fails. self._download_eip_config() else: # XXX: Display a proper message to the user @@ -626,9 +620,11 @@ class MainWindow(QtGui.QMainWindow): systrayMenu = QtGui.QMenu(self) systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - systrayMenu.addAction(self._action_eip_provider) - systrayMenu.addAction(self._action_eip_status) - systrayMenu.addAction(self._action_eip_startstop) + + eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet is OFF")) + eip_menu.addAction(self._action_eip_startstop) + self._status_panel.set_eip_status_menu(eip_menu) + systrayMenu.addAction(self._action_mail_status) systrayMenu.addSeparator() systrayMenu.addAction(self._action_preferences) @@ -682,14 +678,23 @@ class MainWindow(QtGui.QMainWindow): Toggles the window visibility """ visible = self.isVisible() and self.isActiveWindow() + qApp = QtCore.QCoreApplication.instance() + if not visible: + qApp.setQuitOnLastWindowClosed(True) self.show() self.activateWindow() self.raise_() else: + # We set this in order to avoid dialogs shutting down the + # app on close, as they will be the only visible window. + # e.g.: PreferencesWindow, LoggerWindow + qApp.setQuitOnLastWindowClosed(False) self.hide() - self._update_hideshow_menu() + # Wait a bit until the window visibility has changed so + # the menu is set with the correct value. + QtCore.QTimer.singleShot(500, self._update_hideshow_menu) def _center_window(self): """ @@ -957,6 +962,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_enabled(True) def _switch_to_status(self): + # TODO this method name is confusing as hell. """ Changes the stackedWidget index to the EIP status one and triggers the eip bootstrapping @@ -968,12 +974,13 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + # TODO separate UI from logic. + # TODO soledad should check if we want to run only over EIP. self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, self._login_widget.get_user(), self._login_widget.get_password(), - download_if_needed=True, - standalone=self._standalone) + download_if_needed=True) self._download_eip_config() @@ -1048,17 +1055,6 @@ class MainWindow(QtGui.QMainWindow): self._provider_config, self._smtp_config, True) - else: - if self._enabled_services.count(self.MX_SERVICE) > 0: - pass # TODO show MX status - #self._status_panel.set_eip_status( - # self.tr("%s does not support MX") % - # (self._provider_config.get_domain(),), - # error=True) - else: - pass # TODO show MX status - #self._status_panel.set_eip_status( - # self.tr("MX is disabled")) ################################################################### # Service control methods: smtp @@ -1081,7 +1077,12 @@ class MainWindow(QtGui.QMainWindow): logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) return logger.debug("Done bootstrapping SMTP") + self._check_smtp_config() + def _check_smtp_config(self): + """ + Checks smtp config and tries to download smtp client cert if needed. + """ hosts = self._smtp_config.get_hosts() # TODO handle more than one host and define how to choose if len(hosts) > 0: @@ -1089,24 +1090,40 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Using hostname %s for SMTP" % (hostname,)) host = hosts[hostname][self.IP_KEY].encode("utf-8") port = hosts[hostname][self.PORT_KEY] - # TODO move the start to _start_smtp_service - - # TODO Make the encrypted_only configurable - # TODO pick local smtp port in a better way - # TODO remove hard-coded port and let leap.mail set - # the specific default. - - from leap.mail.smtp import setup_smtp_relay - client_cert = self._eip_config.get_client_cert_path( - self._provider_config) - self._smtp_service = setup_smtp_relay( - port=2013, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=client_cert, - smtp_key=client_cert, - encrypted_only=False) + + client_cert = self._smtp_config.get_client_cert_path( + self._provider_config, + about_to_download=True) + + if not os.path.isfile(client_cert): + self._smtp_bootstrapper._download_client_certificates() + if os.path.isfile(client_cert): + self._start_smtp_service(host, port, client_cert) + else: + logger.warning("Tried to download email client " + "certificate, but could not find any") + + else: + logger.warning("No smtp hosts configured") + + def _start_smtp_service(self, host, port, cert): + """ + Starts the smtp service. + """ + # TODO Make the encrypted_only configurable + # TODO pick local smtp port in a better way + # TODO remove hard-coded port and let leap.mail set + # the specific default. + + from leap.mail.smtp import setup_smtp_relay + self._smtp_service = setup_smtp_relay( + port=2013, + keymanager=self._keymanager, + smtp_host=host, + smtp_port=port, + smtp_cert=cert, + smtp_key=cert, + encrypted_only=False) def _stop_smtp_service(self): """ @@ -1174,26 +1191,36 @@ class MainWindow(QtGui.QMainWindow): ################################################################### # Service control methods: eip - def _get_socket_host(self): + def start_eip_machine(self): """ - Returns the socket and port to be used for VPN - - :rtype: tuple (str, str) (host, port) + Initializes and starts the EIP state machine """ - # TODO make this properly multiplatform - # TODO get this out of gui/ + button = self._status_panel.eip_button + action = self._action_eip_startstop + label = self._status_panel.eip_label + builder = statemachines.ConnectionMachineBuilder(self._eip_connection) + eip_machine = builder.make_machine(button=button, + action=action, + label=label) + self.eip_machine = eip_machine + self.eip_machine.start() - if platform.system() == "Windows": - host = "localhost" - port = "9876" - else: - # XXX cleanup this on exit too - host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), - 'openvpn.socket') - port = "unix" + @QtCore.Slot() + def _on_eip_connected(self): + """ + SLOT + TRIGGERS: + self._status_panel.eip_connection_connected + Emits the EIPConnection.qtsigs.connected_signal - return host, port + This is a little workaround for connecting the vpn-connected + signal that currently is beeing processed under status_panel. + After the refactor to EIPConductor this should not be necessary. + """ + logger.debug('EIP connected signal received ...') + self._eip_connection.qtsigs.connected_signal.emit() + @QtCore.Slot() def _start_eip(self): """ SLOT @@ -1204,35 +1231,27 @@ class MainWindow(QtGui.QMainWindow): Starts EIP """ + provider_config = self._get_best_provider_config() + provider = provider_config.get_domain() self._status_panel.eip_pre_up() self.user_stopped_eip = False - provider_config = self._get_best_provider_config() try: - host, port = self._get_socket_host() + # XXX move this to EIPConductor + host, port = get_openvpn_management() self._vpn.start(eipconfig=self._eip_config, providerconfig=provider_config, socket_host=host, socket_port=port) - - self._settings.set_defaultprovider( - provider_config.get_domain()) - - provider = provider_config.get_domain() + self._settings.set_defaultprovider(provider) if self._logged_user is not None: provider = "%s@%s" % (self._logged_user, provider) + # XXX move to the state machine too self._status_panel.set_provider(provider) - self._action_eip_provider.setText(provider_config.get_domain()) - - self._status_panel.eip_started() - - # XXX refactor into status_panel method? - self._action_eip_startstop.setText(self.tr("Turn OFF")) - self._action_eip_startstop.disconnect(self) - self._action_eip_startstop.triggered.connect( - self._stop_eip) + # TODO refactor exceptions so they provide translatable + # usef-facing messages. except EIPNoPolkitAuthAgentAvailable: self._status_panel.set_global_status( # XXX this should change to polkit-kde where @@ -1284,26 +1303,7 @@ class MainWindow(QtGui.QMainWindow): else: self._already_started_eip = True - def _set_eipstatus_off(self): - """ - Sets eip status to off - """ - self._status_panel.set_eip_status(self.tr("OFF"), error=True) - self._status_panel.set_eip_status_icon("error") - self._status_panel.set_startstop_enabled(True) - self._status_panel.eip_stopped() - - self._set_action_eipstart_off() - - def _set_action_eipstart_off(self): - """ - Sets eip startstop action to OFF status. - """ - self._action_eip_startstop.setText(self.tr("Turn ON")) - self._action_eip_startstop.disconnect(self) - self._action_eip_startstop.triggered.connect( - self._start_eip) - + @QtCore.Slot() def _stop_eip(self, abnormal=False): """ SLOT @@ -1327,34 +1327,20 @@ class MainWindow(QtGui.QMainWindow): self._set_eipstatus_off() self._already_started_eip = False + + # XXX do via signal self._settings.set_defaultprovider(None) if self._logged_user: self._status_panel.set_provider( "%s@%s" % (self._logged_user, self._get_best_provider_config().get_domain())) - def _get_best_provider_config(self): + def _set_eipstatus_off(self): """ - Returns the best ProviderConfig to use at a moment. We may - have to use self._provider_config or - self._provisional_provider_config depending on the start - status. - - :rtype: ProviderConfig + Sets eip status to off """ - leap_assert(self._provider_config is not None or - self._provisional_provider_config is not None, - "We need a provider config") - - provider_config = None - if self._provider_config.loaded(): - provider_config = self._provider_config - elif self._provisional_provider_config.loaded(): - provider_config = self._provisional_provider_config - else: - leap_assert(False, "We could not find any usable ProviderConfig.") - - return provider_config + self._status_panel.set_eip_status(self.tr("OFF"), error=True) + self._status_panel.set_eip_status_icon("error") def _download_eip_config(self): """ @@ -1368,6 +1354,7 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \ not self._already_started_eip: + # XXX this should be handled by the state machine. self._status_panel.set_eip_status( self.tr("Starting...")) self._eip_bootstrapper.run_eip_setup_checks( @@ -1381,7 +1368,6 @@ class MainWindow(QtGui.QMainWindow): error=True) else: self._status_panel.set_eip_status(self.tr("Disabled")) - self._status_panel.set_startstop_enabled(False) def _finish_eip_bootstrap(self, data): """ @@ -1402,7 +1388,6 @@ class MainWindow(QtGui.QMainWindow): return provider_config = self._get_best_provider_config() - domain = provider_config.get_domain() loaded = self._eip_config.loaded() @@ -1414,13 +1399,41 @@ class MainWindow(QtGui.QMainWindow): loaded = self._eip_config.load(eip_config_path) if loaded: - self._start_eip() + # DO START EIP Connection! + self._eip_connection.qtsigs.do_connect_signal.emit() else: self._status_panel.set_eip_status( self.tr("Could not load Encrypted Internet " "Configuration."), error=True) + # end eip methods ------------------------------------------- + + def _get_best_provider_config(self): + """ + Returns the best ProviderConfig to use at a moment. We may + have to use self._provider_config or + self._provisional_provider_config depending on the start + status. + + :rtype: ProviderConfig + """ + # TODO move this out of gui. + leap_assert(self._provider_config is not None or + self._provisional_provider_config is not None, + "We need a provider config") + + provider_config = None + if self._provider_config.loaded(): + provider_config = self._provider_config + elif self._provisional_provider_config.loaded(): + provider_config = self._provisional_provider_config + else: + leap_assert(False, "We could not find any usable ProviderConfig.") + + return provider_config + + @QtCore.Slot() def _logout(self): """ SLOT @@ -1444,13 +1457,16 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._logged_user = None - self.ui.action_log_out.setEnabled(False) - self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self._login_widget.set_password("") - self._login_widget.set_enabled(True) - self._login_widget.set_status("") - self.ui.btnPreferences.setEnabled(False) + if ok: + self._logged_user = None + self.ui.action_log_out.setEnabled(False) + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self._login_widget.set_password("") + self._login_widget.set_enabled(True) + self._login_widget.set_status("") + else: + status_text = self.tr("Something went wrong with the logout.") + self._status_panel.set_global_status(status_text, error=True) def _intermediate_stage(self, data): """ @@ -1498,6 +1514,7 @@ class MainWindow(QtGui.QMainWindow): Triggered when the EIP/VPN process finishes to set the UI accordingly. """ + # TODO move to EIPConductor. logger.info("VPN process finished with exitCode %s..." % (exitCode,)) @@ -1532,7 +1549,20 @@ class MainWindow(QtGui.QMainWindow): if exitCode == 0 and IS_MAC: # XXX remove this warning after I fix cocoasudo. logger.warning("The above exit code MIGHT BE WRONG.") - self._stop_eip(abnormal) + + # We emit signals to trigger transitions in the state machine: + qtsigs = self._eip_connection.qtsigs + if abnormal: + signal = qtsigs.connection_died_signal + else: + signal = qtsigs.disconnected_signal + + # XXX verify that the logic kees the same w/o the abnormal flag + # after the refactor to EIPConnection has been completed + # (eipconductor taking the most of the logic under transitions + # that right now are handled under status_panel) + #self._stop_eip(abnormal) + signal.emit() def _on_raise_window_event(self, req): """ @@ -1611,6 +1641,11 @@ class MainWindow(QtGui.QMainWindow): """ # TODO separate the shutting down of services from the # UI stuff. + + # Set this in case that the app is hidden + qApp = QtCore.QCoreApplication.instance() + qApp.setQuitOnLastWindowClosed(True) + self._cleanup_and_quit() self._really_quit = True @@ -1627,37 +1662,3 @@ class MainWindow(QtGui.QMainWindow): self._quit_callback() logger.debug('Bye.') - - -if __name__ == "__main__": - import signal - - def sigint_handler(*args, **kwargs): - logger.debug('SIGINT catched. shutting down...') - mainwindow = args[0] - mainwindow.quit() - - import sys - - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - app = QtGui.QApplication(sys.argv) - mainwindow = MainWindow() - mainwindow.show() - - timer = QtCore.QTimer() - timer.start(500) - timer.timeout.connect(lambda: None) - - sigint = partial(sigint_handler, mainwindow) - signal.signal(signal.SIGINT, sigint) - - sys.exit(app.exec_()) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 1becfb18..2d17f6c2 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -24,6 +24,7 @@ import logging from functools import partial from PySide import QtCore, QtGui +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.soledad.client import NoStorageSecret from leap.bitmask.crypto.srpauth import SRPAuthBadPassword @@ -40,26 +41,18 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ - - WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") - - def __init__(self, parent, srp_auth, leap_settings, standalone): + def __init__(self, parent, srp_auth): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param srp_auth: SRPAuth object configured in the main app. :type srp_auth: SRPAuth - :param standalone: If True, the application is running as standalone - and the preferences dialog should display some - messages according to this. - :type standalone: bool """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._srp_auth = srp_auth - self._settings = leap_settings - self._standalone = standalone + self._settings = LeapSettings() self._soledad = None # Load UI @@ -325,8 +318,7 @@ class PreferencesWindow(QtGui.QDialog): for service in services: try: checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name( - service, self._standalone) + service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.vlServices.addWidget(checkbox) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py new file mode 100644 index 00000000..c3dd5ed3 --- /dev/null +++ b/src/leap/bitmask/gui/statemachines.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +# statemachines.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +State machines for the Bitmask app. +""" +import logging + +from PySide.QtCore import QStateMachine, QState +from PySide.QtCore import QObject + +from leap.bitmask.services import connections +from leap.common.check import leap_assert_type + +logger = logging.getLogger(__name__) + +_tr = QObject().tr + +# Indexes for the state dict +_ON = "on" +_OFF = "off" +_CON = "connecting" +_DIS = "disconnecting" + + +class IntermediateState(QState): + """ + Intermediate state that emits a custom signal on entry + """ + def __init__(self, signal): + """ + Initializer. + :param signal: the signal to be emitted on entry on this state. + :type signal: QtCore.QSignal + """ + super(IntermediateState, self).__init__() + self._signal = signal + + def onEntry(self, *args): + """ + Emits the signal on entry. + """ + logger.debug('IntermediateState entered. Emitting signal ...') + if self._signal is not None: + self._signal.emit() + + +class ConnectionMachineBuilder(object): + """ + Builder class for state machines made from LEAPConnections. + """ + def __init__(self, connection): + """ + :param connection: an instance of a concrete LEAPConnection + we will be building a state machine for. + :type connection: AbstractLEAPConnection + """ + self._conn = connection + leap_assert_type(self._conn, connections.AbstractLEAPConnection) + + def make_machine(self, button=None, action=None, label=None): + """ + Creates a statemachine associated with the passed controls. + + :param button: the switch button. + :type button: QPushButton + + :param action: the actionh that controls connection switch in a menu. + :type action: QAction + + :param label: the label that displays the connection state + :type label: QLabel + + :returns: a state machine + :rtype: QStateMachine + """ + machine = QStateMachine() + conn = self._conn + + states = self._make_states(button, action, label) + + # transitions: + + states[_OFF].addTransition( + conn.qtsigs.do_connect_signal, + states[_CON]) + + # * Clicking the buttons or actions transitions to the + # intermediate stage. + if button: + states[_OFF].addTransition( + button.clicked, + states[_CON]) + states[_ON].addTransition( + button.clicked, + states[_DIS]) + + if action: + states[_OFF].addTransition( + action.triggered, + states[_CON]) + states[_ON].addTransition( + action.triggered, + states[_DIS]) + + # * We transition to the completed stages when + # we receive the matching signal from the underlying + # conductor. + + states[_CON].addTransition( + conn.qtsigs.connected_signal, + states[_ON]) + states[_DIS].addTransition( + conn.qtsigs.disconnected_signal, + states[_OFF]) + + # * If we receive the connection_died, we transition + # to the off state + states[_ON].addTransition( + conn.qtsigs.connection_died_signal, + states[_OFF]) + + # adding states to the machine + for state in states.itervalues(): + machine.addState(state) + machine.setInitialState(states[_OFF]) + return machine + + def _make_states(self, button, action, label): + """ + Creates the four states for the state machine + + :param button: the switch button. + :type button: QPushButton + + :param action: the actionh that controls connection switch in a menu. + :type action: QAction + + :param label: the label that displays the connection state + :type label: QLabel + + :returns: a dict of states + :rtype: dict + """ + conn = self._conn + states = {} + + # TODO add tooltip + + # OFF State ---------------------- + off = QState() + off_label = _tr("Turn {0}").format( + conn.Connected.short_label) + if button: + off.assignProperty( + button, 'text', off_label) + off.assignProperty( + button, 'enabled', True) + if action: + off.assignProperty( + action, 'text', off_label) + off.setObjectName(_OFF) + states[_OFF] = off + + # CONNECTING State ---------------- + connecting = IntermediateState( + conn.qtsigs.connecting_signal) + on_label = _tr("Turn {0}").format( + conn.Disconnected.short_label) + if button: + connecting.assignProperty( + button, 'text', on_label) + connecting.assignProperty( + button, 'enabled', False) + if action: + connecting.assignProperty( + action, 'text', on_label) + connecting.assignProperty( + action, 'enabled', False) + connecting.setObjectName(_CON) + states[_CON] = connecting + + # ON State ------------------------ + on = QState() + if button: + on.assignProperty( + button, 'text', on_label) + on.assignProperty( + button, 'enabled', True) + if action: + on.assignProperty( + action, 'text', on_label) + on.assignProperty( + action, 'enabled', True) + # TODO set label for ON state + on.setObjectName(_ON) + states[_ON] = on + + # DISCONNECTING State ------------- + disconnecting = IntermediateState( + conn.qtsigs.disconnecting_signal) + if button: + disconnecting.assignProperty( + button, 'enabled', False) + # XXX complete disconnecting + # TODO disable button + disconnecting.setObjectName(_DIS) + states[_DIS] = disconnecting + + return states diff --git a/src/leap/bitmask/gui/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py index 3a91f08e..679f00b1 100644 --- a/src/leap/bitmask/gui/statuspanel.py +++ b/src/leap/bitmask/gui/statuspanel.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Status Panel widget implementation """ @@ -25,9 +24,10 @@ from functools import partial from PySide import QtCore, QtGui +from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.services.eip.vpnprocess import VPNManager from leap.bitmask.platform_init import IS_WIN, IS_LINUX -from leap.bitmask.util import first +from leap.bitmask.util.averages import RateMovingAverage from leap.common.check import leap_assert, leap_assert_type from leap.common.events import register from leap.common.events import events_pb2 as proto @@ -37,83 +37,10 @@ from ui_statuspanel import Ui_StatusPanel logger = logging.getLogger(__name__) -class RateMovingAverage(object): - """ - Moving window average for calculating - upload and download rates. - """ - SAMPLE_SIZE = 5 - - def __init__(self): - """ - Initializes an empty array of fixed size - """ - self.reset() - - def reset(self): - self._data = [None for i in xrange(self.SAMPLE_SIZE)] - - def append(self, x): - """ - Appends a new data point to the collection. - - :param x: A tuple containing timestamp and traffic points - in the form (timestamp, traffic) - :type x: tuple - """ - self._data.pop(0) - self._data.append(x) - - def get(self): - """ - Gets the collection. - """ - return self._data - - def get_average(self): - """ - Gets the moving average. - """ - data = filter(None, self.get()) - traff = [traffic for (ts, traffic) in data] - times = [ts for (ts, traffic) in data] - - try: - deltatraffic = traff[-1] - first(traff) - deltat = (times[-1] - first(times)).seconds - except IndexError: - deltatraffic = 0 - deltat = 0 - - try: - rate = float(deltatraffic) / float(deltat) / 1024 - except ZeroDivisionError: - rate = 0 - - # In some cases we get negative rates - if rate < 0: - rate = 0 - - return rate - - def get_total(self): - """ - Gets the total accumulated throughput. - """ - try: - return self._data[-1][1] / 1024 - except TypeError: - return 0 - - class StatusPanelWidget(QtGui.QWidget): """ Status widget that displays the current state of the LEAP services """ - - start_eip = QtCore.Signal() - stop_eip = QtCore.Signal() - DISPLAY_TRAFFIC_RATES = True RATE_STR = "%14.2f KB/s" TOTAL_STR = "%14.2f Kb" @@ -121,6 +48,7 @@ class StatusPanelWidget(QtGui.QWidget): MAIL_OFF_ICON = ":/images/mail-unlocked.png" MAIL_ON_ICON = ":/images/mail-locked.png" + eip_connection_connected = QtCore.Signal() _soledad_event = QtCore.Signal(object) _smtp_event = QtCore.Signal(object) _imap_event = QtCore.Signal(object) @@ -130,17 +58,18 @@ class StatusPanelWidget(QtGui.QWidget): QtGui.QWidget.__init__(self, parent) self._systray = None - self._action_eip_status = None + self._eip_status_menu = None self.ui = Ui_StatusPanel() self.ui.setupUi(self) - self.ui.btnEipStartStop.setEnabled(False) - self.ui.btnEipStartStop.clicked.connect( - self.start_eip) + self.eipconnection = EIPConnection() self.hide_status_box() + # set systray tooltip statuses + self._eip_status = self._mx_status = "" + # Set the EIP status icons self.CONNECTING_ICON = None self.CONNECTED_ICON = None @@ -327,6 +256,8 @@ class StatusPanelWidget(QtGui.QWidget): self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) + # Systray and actions + def set_systray(self, systray): """ Sets the systray object to use. @@ -336,6 +267,16 @@ class StatusPanelWidget(QtGui.QWidget): """ leap_assert_type(systray, QtGui.QSystemTrayIcon) self._systray = systray + self._systray.setToolTip(self.tr("All services are OFF")) + + def _update_systray_tooltip(self): + """ + Updates the system tray icon tooltip using the eip and mx statuses. + """ + status = self.tr("Encrypted Internet is {0}").format(self._eip_status) + status += '\n' + status += self.tr("Mail is {0}").format(self._mx_status) + self._systray.setToolTip(status) def set_action_eip_startstop(self, action_eip_startstop): """ @@ -346,15 +287,15 @@ class StatusPanelWidget(QtGui.QWidget): """ self._action_eip_startstop = action_eip_startstop - def set_action_eip_status(self, action_eip_status): + def set_eip_status_menu(self, eip_status_menu): """ - Sets the action_eip_status to use. + Sets the eip_status_menu to use. - :param action_eip_status: action_eip_status to be used - :type action_eip_status: QtGui.QAction + :param eip_status_menu: eip_status_menu to be used + :type eip_status_menu: QtGui.QMenu """ - leap_assert_type(action_eip_status, QtGui.QAction) - self._action_eip_status = action_eip_status + leap_assert_type(eip_status_menu, QtGui.QMenu) + self._eip_status_menu = eip_status_menu def set_action_mail_status(self, action_mail_status): """ @@ -388,6 +329,25 @@ class StatusPanelWidget(QtGui.QWidget): """ self.ui.globalStatusBox.hide() + # EIP status --- + + @property + def eip_button(self): + return self.ui.btnEipStartStop + + @property + def eip_label(self): + return self.ui.lblEIPStatus + + def eip_pre_up(self): + """ + Triggered when the app activates eip. + Hides the status box and disables the start/stop button. + """ + self.hide_status_box() + self.set_startstop_enabled(False) + + # XXX disable (later) -------------------------- def set_eip_status(self, status, error=False): """ Sets the status label at the VPN stage to status @@ -400,11 +360,14 @@ class StatusPanelWidget(QtGui.QWidget): """ leap_assert_type(error, bool) - self._systray.setToolTip(status) + self._eip_status = status + if error: status = "<font color='red'>%s</font>" % (status,) self.ui.lblEIPStatus.setText(status) + self._update_systray_tooltip() + # XXX disable --------------------------------- def set_startstop_enabled(self, value): """ Enable or disable btnEipStartStop and _action_eip_startstop @@ -417,14 +380,7 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.setEnabled(value) self._action_eip_startstop.setEnabled(value) - def eip_pre_up(self): - """ - Triggered when the app activates eip. - Hides the status box and disables the start/stop button. - """ - self.hide_status_box() - self.set_startstop_enabled(False) - + # XXX disable ----------------------------- def eip_started(self): """ Sets the state of the widget to how it should look after EIP @@ -433,27 +389,21 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( - self.stop_eip) + self.eipconnection.qtsigs.do_connect_signal) + # XXX disable ----------------------------- def eip_stopped(self): """ Sets the state of the widget to how it should look after EIP has stopped """ + # XXX should connect this to EIPConnection.disconnected_signal self._reset_traffic_rates() + # XXX disable ----------------------------- self.ui.btnEipStartStop.setText(self.tr("Turn ON")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( - self.start_eip) - - def set_icon(self, icon): - """ - Sets the icon to display for EIP - - :param icon: icon to display - :type icon: QPixmap - """ - self.ui.lblVPNStatusIcon.setPixmap(icon) + self.eipconnection.qtsigs.do_disconnect_signal) def update_vpn_status(self, data): """ @@ -492,14 +442,21 @@ class StatusPanelWidget(QtGui.QWidget): TRIGGER: VPN.state_changed Updates the displayed VPN state based on the data provided by - the VPN thread + the VPN thread. + + Emits: + If the status is connected, we emit EIPConnection.qtsigs. + connected_signal """ status = data[VPNManager.STATUS_STEP_KEY] self.set_eip_status_icon(status) if status == "CONNECTED": + # XXX should be handled by the state machine too. self.set_eip_status(self.tr("ON")) - # Only now we can properly enable the button. - self.set_startstop_enabled(True) + logger.debug("STATUS IS CONNECTED --- emitting signal") + self.eip_connection_connected.emit() + + # XXX should lookup status map in EIPConnection elif status == "AUTH": self.set_eip_status(self.tr("Authenticating...")) elif status == "GET_CONFIG": @@ -513,7 +470,8 @@ class StatusPanelWidget(QtGui.QWidget): elif status == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly - QtCore.QTimer.singleShot(0, self.stop_eip) + QtCore.QTimer.singleShot( + 0, self.eipconnection.qtsigs.do_disconnect_signal) QtCore.QTimer.singleShot(0, partial(self.set_global_status, self.tr("Unable to start VPN, " "it's already " @@ -521,6 +479,15 @@ class StatusPanelWidget(QtGui.QWidget): else: self.set_eip_status(status) + def set_eip_icon(self, icon): + """ + Sets the icon to display for EIP + + :param icon: icon to display + :type icon: QPixmap + """ + self.ui.lblVPNStatusIcon.setPixmap(icon) + def set_eip_status_icon(self, status): """ Given a status step from the VPN thread, set the icon properly @@ -530,27 +497,31 @@ class StatusPanelWidget(QtGui.QWidget): """ selected_pixmap = self.ERROR_ICON selected_pixmap_tray = self.ERROR_ICON_TRAY - tray_message = self.tr("Encryption is OFF") + tray_message = self.tr("Encrypted Internet is OFF") if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON selected_pixmap_tray = self.CONNECTING_ICON_TRAY - tray_message = self.tr("Turning ON") + tray_message = self.tr("Encrypted Internet is STARTING") elif status in ("CONNECTED"): - tray_message = self.tr("Encryption is ON") + tray_message = self.tr("Encrypted Internet is ON") selected_pixmap = self.CONNECTED_ICON selected_pixmap_tray = self.CONNECTED_ICON_TRAY - self.set_icon(selected_pixmap) + self.set_eip_icon(selected_pixmap) self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray)) - self._action_eip_status.setText(tray_message) + self._eip_status_menu.setTitle(tray_message) def set_provider(self, provider): self.ui.lblProvider.setText(provider) + # + # mail methods + # + def _set_mail_status(self, status, ready=False): """ - Sets the Encrypted Mail status in the label and in the tray icon. + Sets the Mail status in the label and in the tray icon. :param status: the status text to display :type status: unicode @@ -559,15 +530,18 @@ class StatusPanelWidget(QtGui.QWidget): """ self.ui.lblMailStatus.setText(status) - tray_status = self.tr('Encrypted Mail is OFF') + self._mx_status = self.tr('OFF') + tray_status = self.tr('Mail is OFF') icon = QtGui.QPixmap(self.MAIL_OFF_ICON) if ready: icon = QtGui.QPixmap(self.MAIL_ON_ICON) - tray_status = self.tr('Encrypted Mail is ON') + self._mx_status = self.tr('ON') + tray_status = self.tr('Mail is ON') self.ui.lblMailIcon.setPixmap(icon) self._action_mail_status.setText(tray_status) + self._update_systray_tooltip() def _mail_handle_soledad_events(self, req): """ @@ -724,7 +698,7 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.lblUnread.setVisible(req.content != "0") self._set_mail_status(self.tr("ON"), ready=True) else: - leap_assert(False, + leap_assert(False, # XXX ??? "Don't know how to handle this state: %s" % (req.event)) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index e004e6cf..45734b81 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -27,6 +27,7 @@ from functools import partial from PySide import QtCore, QtGui from twisted.internet import threads +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.util.privilege_policies import is_missing_policy_permissions @@ -58,21 +59,16 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, standalone=False, bypass_checks=False): + def __init__(self, bypass_checks=False): """ Constructor for the main Wizard. - :param standalone: If True, the application is running as standalone - and the wizard should display some messages according to this. - :type standalone: bool :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap :type bypass_checks: bool """ QtGui.QWizard.__init__(self) - self.standalone = standalone - self.ui = Ui_Wizard() self.ui.setupUi(self) @@ -489,8 +485,7 @@ class Wizard(QtGui.QWizard): try: if service not in self._shown_services: checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name( - service, self.standalone) + service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.serviceListLayout.addWidget(checkbox) @@ -555,15 +550,6 @@ class Wizard(QtGui.QWizard): if pageId == self.SERVICES_PAGE: self._populate_services() - def _is_need_eip_password_warning(self): - """ - Returns True if we need to add a warning about eip needing - administrative permissions to start. That can be either - because we are running in standalone mode, or because we could - not find the needed privilege escalation mechanisms being operative. - """ - return self.standalone or is_missing_policy_permissions() - def nextId(self): """ Sets the next page id for the wizard based on wether the user diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 339f9cc6..afce72f6 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -17,13 +17,29 @@ """ Services module. """ +import logging +import os + from PySide import QtCore + +from leap.bitmask.config import flags +from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.util.privilege_policies import is_missing_policy_permissions +from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix + +from leap.common.check import leap_assert +from leap.common.config.baseconfig import BaseConfig +from leap.common.files import get_mtime + +logger = logging.getLogger(__name__) + DEPLOYED = ["openvpn", "mx"] -def get_service_display_name(service, standalone=False): +def get_service_display_name(service): """ Returns the name to display of the given service. If there is no configured name for that service, then returns the same @@ -31,9 +47,6 @@ def get_service_display_name(service, standalone=False): :param service: the 'machine' service name :type service: str - :param standalone: True if the app is running in a standalone mode, used - to display messages according that. - :type standalone: bool :rtype: str """ @@ -53,7 +66,7 @@ def get_service_display_name(service, standalone=False): # administrative permissions to start. That can be either # because we are running in standalone mode, or because we could # not find the needed privilege escalation mechanisms being operative. - if standalone or is_missing_policy_permissions(): + if flags.STANDALONE or is_missing_policy_permissions(): EIP_LABEL += " " + _tr("(will need admin password to start)") return service_display.get(service, service) @@ -70,3 +83,87 @@ def get_supported(services): :rtype: list of str """ return filter(lambda s: s in DEPLOYED, services) + + +def download_service_config(provider_config, service_config, + session, + download_if_needed=True): + """ + Downloads config for a given service. + + :param provider_config: an instance of ProviderConfig + :type provider_config: ProviderConfig + + :param service_config: an instance of a particular Service config. + :type service_config: BaseConfig + + :param session: an instance of a fetcher.session + (currently we're using requests only, but it can be + anything that implements that interface) + :type session: requests.sessions.Session + """ + service_name = service_config.name + service_json = "{0}-service.json".format(service_name) + headers = {} + mtime = get_mtime(os.path.join(get_path_prefix(), + "leap", "providers", + provider_config.get_domain(), + service_json)) + if download_if_needed and mtime: + headers['if-modified-since'] = mtime + + api_version = provider_config.get_api_version() + + config_uri = "%s/%s/config/%s-service.json" % ( + provider_config.get_api_uri(), + api_version, + service_name) + logger.debug('Downloading %s config from: %s' % ( + service_name.upper(), + config_uri)) + + # XXX make and use @with_srp_auth decorator + srp_auth = SRPAuth(provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + + res = session.get(config_uri, + verify=provider_config.get_ca_cert_path(), + headers=headers, + timeout=REQUEST_TIMEOUT, + cookies=cookies) + res.raise_for_status() + + service_config.set_api_version(api_version) + + # Not modified + service_path = ("leap", "providers", provider_config.get_domain(), + service_json) + if res.status_code == 304: + logger.debug( + "{0} definition has not been modified".format( + service_name.upper())) + service_config.load(os.path.join(*service_path)) + else: + service_definition, mtime = get_content(res) + service_config.load(data=service_definition, mtime=mtime) + service_config.save(service_path) + + +class ServiceConfig(BaseConfig): + """ + Base class used by the different service configs + """ + + _service_name = None + + @property + def name(self): + """ + Getter for the service name. + Derived classes should assign it. + """ + leap_assert(self._service_name is not None) + return self._service_name diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py new file mode 100644 index 00000000..f3ab9e8e --- /dev/null +++ b/src/leap/bitmask/services/connections.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# connections.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Abstract LEAP connections. +""" +# TODO use zope.interface instead +from abc import ABCMeta + +from PySide import QtCore + +from leap.common.check import leap_assert + +_tr = QtCore.QObject().tr + + +class State(object): + """ + Abstract state class + """ + __metaclass__ = ABCMeta + + label = None + short_label = None + +""" +The different services should declare a ServiceConnection class that +inherits from AbstractLEAPConnection, so an instance of such class +can be used to inform the StateMachineBuilder of the particularities +of the state transitions for each particular connection. + +In the future, we will extend this class to allow composites in connections, +so we can apply conditional logic to the transitions. +""" + + +class AbstractLEAPConnection(object): + """ + Abstract LEAP Connection class. + + This class is likely to undergo heavy transformations + in the coming releases, to better accomodate the use cases + of the different connections that we use in the Bitmask + client. + """ + __metaclass__ = ABCMeta + + _connection_name = None + + @property + def name(self): + """ + Name of the connection + """ + con_name = self._connection_name + leap_assert(con_name is not None) + return con_name + + _qtsigs = None + + @property + def qtsigs(self): + """ + Object that encapsulates the Qt Signals emitted + by this connection. + """ + return self._qtsigs + + # XXX for conditional transitions with composites, + # we might want to add + # a field with dependencies: what this connection + # needs for (ON) state. + # XXX Look also at child states in the state machine. + #depends = () + + # Signals that derived classes + # have to implement. + + # Commands + do_connect_signal = None + do_disconnect_signal = None + + # Intermediate stages + connecting_signal = None + disconnecting_signal = None + + # Complete stages + connected_signal = None + disconnected_signal = None + + # Bypass stages + connection_died_signal = None + + class Disconnected(State): + """Disconnected state""" + label = _tr("Disconnected") + short_label = _tr("OFF") + + class Connected(State): + """Connected state""" + label = _tr("Connected") + short_label = _tr("ON") + + class Connecting(State): + """Connecting state""" + label = _tr("Connecting") + short_label = _tr("...") + + class Disconnecting(State): + """Disconnecting state""" + label = _tr("Disconnecting") + short_label = _tr("...") diff --git a/src/leap/bitmask/services/eip/__init__.py b/src/leap/bitmask/services/eip/__init__.py index e69de29b..dd010027 100644 --- a/src/leap/bitmask/services/eip/__init__.py +++ b/src/leap/bitmask/services/eip/__init__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +leap.bitmask.services.eip module initialization +""" +import os +import tempfile + +from leap.bitmask.platform_init import IS_WIN + + +def get_openvpn_management(): + """ + Returns the socket and port to be used for VPN + + :rtype: tuple (str, str) (host, port) + """ + if IS_WIN: + host = "localhost" + port = "9876" + else: + # XXX cleanup this on exit too + # XXX atexit.register ? + host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), + 'openvpn.socket') + port = "unix" + + return host, port diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py new file mode 100644 index 00000000..5f05ba07 --- /dev/null +++ b/src/leap/bitmask/services/eip/connection.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# connection.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +EIP Connection +""" +from PySide import QtCore + +from leap.bitmask.services.connections import AbstractLEAPConnection + + +class EIPConnectionSignals(QtCore.QObject): + """ + Qt Signals used by EIPConnection + """ + # commands + do_connect_signal = QtCore.Signal() + do_disconnect_signal = QtCore.Signal() + + # intermediate stages + # this is currently binded to mainwindow._start_eip + connecting_signal = QtCore.Signal() + # this is currently binded to mainwindow._stop_eip + disconnecting_signal = QtCore.Signal() + + connected_signal = QtCore.Signal() + disconnected_signal = QtCore.Signal() + + connection_died_signal = QtCore.Signal() + + +class EIPConnection(AbstractLEAPConnection): + + def __init__(self): + self._qtsigs = EIPConnectionSignals() diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index 6393e53a..885c4420 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -14,25 +14,23 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ EIP bootstrapping """ - import logging import os from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.srpauth import SRPAuth -from leap.bitmask.services.eip.eipconfig import EIPConfig -from leap.bitmask.util.request_helpers import get_content -from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.bitmask.crypto.certs import download_client_cert +from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper -from leap.common import certs +from leap.bitmask.services.eip.eipconfig import EIPConfig +from leap.common import certs as leap_certs +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p +from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -63,50 +61,15 @@ class EIPBootstrapper(AbstractBootstrapper): leap_assert(self._provider_config, "We need a provider configuration!") - logger.debug("Downloading EIP config for %s" % (self._provider_config.get_domain(),)) - api_version = self._provider_config.get_api_version() self._eip_config = EIPConfig() - self._eip_config.set_api_version(api_version) - - headers = {} - mtime = get_mtime(os.path.join(self._eip_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - # it's in 1/config/eip, config/eip and config/1/eip... - config_uri = "%s/%s/config/eip-service.json" % ( - self._provider_config.get_api_uri(), - api_version) - logger.debug('Downloading eip config from: %s' % config_uri) - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("EIP definition has not been modified") - else: - eip_definition, mtime = get_content(res) - - self._eip_config.load(data=eip_definition, mtime=mtime) - self._eip_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json"]) + download_service_config( + self._provider_config, + self._eip_config, + self._session, + self._download_if_needed) def _download_client_certificates(self, *args): """ @@ -124,40 +87,17 @@ class EIPBootstrapper(AbstractBootstrapper): # For re-download if something is wrong with the cert self._download_if_needed = self._download_if_needed and \ - not certs.should_redownload(client_cert_path) + not leap_certs.should_redownload(client_cert_path) if self._download_if_needed and \ - os.path.exists(client_cert_path): + os.path.isfile(client_cert_path): check_and_fix_urw_only(client_cert_path) return - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - cert_uri = "%s/%s/cert" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('getting cert from uri: %s' % cert_uri) - res = self._session.get(cert_uri, - verify=self._provider_config - .get_ca_cert_path(), - cookies=cookies, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - client_cert = res.content - - if not certs.is_valid_pemfile(client_cert): - raise Exception(self.tr("The downloaded certificate is not a " - "valid PEM file")) - - mkdir_p(os.path.dirname(client_cert_path)) - - with open(client_cert_path, "w") as f: - f.write(client_cert) - - check_and_fix_urw_only(client_cert_path) + download_client_cert( + self._provider_config, + client_cert_path, + self._session) def run_eip_setup_checks(self, provider_config, diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 1cb7419e..7d8995b4 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Provider configuration """ @@ -26,9 +25,10 @@ import time import ipaddr from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.eip.eipspec import get_schema +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.config.baseconfig import BaseConfig logger = logging.getLogger(__name__) @@ -144,15 +144,17 @@ class VPNGatewaySelector(object): return -local_offset / 3600 -class EIPConfig(BaseConfig): +class EIPConfig(ServiceConfig): """ Provider configuration abstraction class """ + _service_name = "eip" + OPENVPN_ALLOWED_KEYS = ("auth", "cipher", "tls-cipher") OPENVPN_CIPHERS_REGEX = re.compile("[A-Z0-9\-]+") def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) self._api_version = None def _get_schema(self): @@ -236,13 +238,10 @@ class EIPConfig(BaseConfig): leap_assert(providerconfig, "We need a provider") leap_assert_type(providerconfig, ProviderConfig) - cert_path = os.path.join(self.get_path_prefix(), - "leap", - "providers", + cert_path = os.path.join(get_path_prefix(), + "leap", "providers", providerconfig.get_domain(), - "keys", - "client", - "openvpn.pem") + "keys", "client", "openvpn.pem") if not about_to_download: leap_assert(os.path.exists(cert_path), diff --git a/src/leap/bitmask/services/eip/providerbootstrapper.py b/src/leap/bitmask/services/eip/providerbootstrapper.py index ac3a44db..3b7c9899 100644 --- a/src/leap/bitmask/services/eip/providerbootstrapper.py +++ b/src/leap/bitmask/services/eip/providerbootstrapper.py @@ -28,6 +28,7 @@ from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.provider.supportedapis import SupportedAPIs @@ -133,9 +134,8 @@ class ProviderBootstrapper(AbstractBootstrapper): headers = {} - provider_json = os.path.join( - ProviderConfig().get_path_prefix(), "leap", "providers", - self._domain, "provider.json") + provider_json = os.path.join(get_path_prefix(), "leap", "providers", + self._domain, "provider.json") mtime = get_mtime(provider_json) if self._download_if_needed and mtime: diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py index a50da8b9..daa0d81f 100644 --- a/src/leap/bitmask/services/eip/vpnlaunchers.py +++ b/src/leap/bitmask/services/eip/vpnlaunchers.py @@ -34,16 +34,19 @@ from abc import ABCMeta, abstractmethod from functools import partial from time import sleep +from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector from leap.bitmask.util import first +from leap.bitmask.util import get_path_prefix from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.bitmask.util import privilege_policies from leap.common.check import leap_assert, leap_assert_type from leap.common.files import which + logger = logging.getLogger(__name__) @@ -98,15 +101,12 @@ class VPNLauncher(object): return [] @abstractmethod - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ return {} @@ -220,14 +220,13 @@ def _is_auth_agent_running(): return any(is_running) -def _try_to_launch_agent(standalone=False): +def _try_to_launch_agent(): """ Tries to launch a polkit daemon. """ env = None - if standalone is True: - env = { - "PYTHONPATH": os.path.abspath('../../../../lib/')} + if flags.STANDALONE is True: + env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} try: # We need to quote the command because subprocess call # will do "sh -c 'foo'", so if we do not quoute it we'll end @@ -247,8 +246,7 @@ class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' OPENVPN_BIN = 'openvpn' OPENVPN_BIN_PATH = os.path.join( - ProviderConfig().get_path_prefix(), - "..", "apps", "eip", OPENVPN_BIN) + get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) SYSTEM_CONFIG = "/etc/leap" UP_DOWN_FILE = "resolv-update" @@ -320,7 +318,7 @@ class LinuxVPNLauncher(VPNLauncher): """ if _is_pkexec_in_system(): if not _is_auth_agent_running(): - _try_to_launch_agent(ProviderConfig.standalone) + _try_to_launch_agent() sleep(0.5) if _is_auth_agent_running(): pkexec_possibilities = which(kls.PKEXEC_BIN) @@ -397,10 +395,9 @@ class LinuxVPNLauncher(VPNLauncher): leap_assert(socket_port, "We need a socket port!") kwargs = {} - if ProviderConfig.standalone: + if flags.STANDALONE: kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") + get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) @@ -423,7 +420,7 @@ class LinuxVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -513,23 +510,17 @@ class LinuxVPNLauncher(VPNLauncher): return [openvpn] + args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - - return {"LD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} + return { + "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } class DarwinVPNLauncher(VPNLauncher): @@ -664,10 +655,9 @@ class DarwinVPNLauncher(VPNLauncher): raise EIPNoTunKextLoaded kwargs = {} - if ProviderConfig.standalone: + if flags.STANDALONE: kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") + get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which( self.OPENVPN_BIN, @@ -686,7 +676,7 @@ class DarwinVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -787,20 +777,17 @@ class DarwinVPNLauncher(VPNLauncher): return [command] + cmd_args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ - return {"DYLD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} + return { + "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } class WindowsVPNLauncher(VPNLauncher): @@ -852,7 +839,7 @@ class WindowsVPNLauncher(VPNLauncher): openvpn_possibilities = which( self.OPENVPN_BIN, - path_extension=os.path.join(providerconfig.get_path_prefix(), + path_extension=os.path.join(get_path_prefix(), "..", "apps", "eip")) if len(openvpn_possibilities) == 0: @@ -869,7 +856,7 @@ class WindowsVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -936,15 +923,12 @@ class WindowsVPNLauncher(VPNLauncher): return [openvpn] + args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ return {} diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index a896b60c..15ac812b 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -95,6 +95,7 @@ class VPN(object): self._reactor = reactor self._qtsigs = VPNSignals() + # XXX should get it from config.flags self._openvpn_verb = kwargs.get(self.OPENVPN_VERB, None) @property @@ -536,7 +537,7 @@ class VPNManager(object): """ Return a dict containing the vpn environment to be used. """ - return self._launcher.get_vpn_env(self._providerconfig) + return self._launcher.get_vpn_env() def terminate_openvpn(self, shutdown=False): """ diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 0e83424c..032d6357 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -14,22 +14,21 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ SMTP bootstrapping """ - import logging import os from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.srpauth import SRPAuth -from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.crypto.certs import download_client_cert +from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper +from leap.common import certs as leap_certs from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import get_mtime +from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -61,55 +60,45 @@ class SMTPBootstrapper(AbstractBootstrapper): logger.debug("Downloading SMTP config for %s" % (self._provider_config.get_domain(),)) - headers = {} - mtime = get_mtime(os.path.join(self._smtp_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - api_version = self._provider_config.get_api_version() - - # there is some confusion with this uri, - config_uri = "%s/%s/config/smtp-service.json" % ( - self._provider_config.get_api_uri(), api_version) - - logger.debug('Downloading SMTP config from: %s' % config_uri) - - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - cookies=cookies) - res.raise_for_status() - - self._smtp_config.set_api_version(api_version) - - # Not modified - if res.status_code == 304: - logger.debug("SMTP definition has not been modified") - self._smtp_config.load(os.path.join( - "leap", "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - else: - smtp_definition, mtime = get_content(res) - - self._smtp_config.load(data=smtp_definition, mtime=mtime) - self._smtp_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json"]) + download_service_config( + self._provider_config, + self._smtp_config, + self._session, + self._download_if_needed) + + def _download_client_certificates(self, *args): + """ + Downloads the SMTP client certificate for the given provider + + We actually are downloading the certificate for the same uri as + for the EIP config, but we duplicate these bits to allow mail + service to be working in a provider that does not offer EIP. + """ + # TODO factor out with eipboostrapper.download_client_certificates + # TODO this shouldn't be a private method, it's called from + # mainwindow. + leap_assert(self._provider_config, "We need a provider configuration!") + leap_assert(self._smtp_config, "We need an smtp configuration!") + + logger.debug("Downloading SMTP client certificate for %s" % + (self._provider_config.get_domain(),)) + + client_cert_path = self._smtp_config.\ + get_client_cert_path(self._provider_config, + about_to_download=True) + + # For re-download if something is wrong with the cert + self._download_if_needed = self._download_if_needed and \ + not leap_certs.should_redownload(client_cert_path) + + if self._download_if_needed and \ + os.path.isfile(client_cert_path): + check_and_fix_urw_only(client_cert_path) + return + + download_client_cert(self._provider_config, + client_cert_path, + self._session) def run_smtp_setup_checks(self, provider_config, diff --git a/src/leap/bitmask/services/mail/smtpconfig.py b/src/leap/bitmask/services/mail/smtpconfig.py index 20041c30..09f90314 100644 --- a/src/leap/bitmask/services/mail/smtpconfig.py +++ b/src/leap/bitmask/services/mail/smtpconfig.py @@ -14,25 +14,29 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ SMTP configuration """ import logging +import os +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.mail.smtpspec import get_schema -from leap.common.config.baseconfig import BaseConfig +from leap.bitmask.util import get_path_prefix +from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) -class SMTPConfig(BaseConfig): +class SMTPConfig(ServiceConfig): """ SMTP configuration abstraction class """ + _service_name = "smtp" def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) def _get_schema(self): """ @@ -47,3 +51,25 @@ class SMTPConfig(BaseConfig): def get_locations(self): return self._safe_get_value("locations") + + def get_client_cert_path(self, + providerconfig=None, + about_to_download=False): + """ + Returns the path to the certificate used by smtp + """ + + leap_assert(providerconfig, "We need a provider") + leap_assert_type(providerconfig, ProviderConfig) + + cert_path = os.path.join(get_path_prefix(), + "leap", "providers", + providerconfig.get_domain(), + "keys", "client", "smtp.pem") + + if not about_to_download: + leap_assert(os.path.exists(cert_path), + "You need to download the certificate first") + logger.debug("Using SMTP cert %s" % (cert_path,)) + + return cert_path diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 3bbfea85..cac91440 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -26,11 +26,13 @@ import socket from PySide import QtCore from u1db import errors as u1db_errors +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type from leap.common.files import get_mtime from leap.keymanager import KeyManager, openpgp @@ -120,8 +122,7 @@ class SoledadBootstrapper(AbstractBootstrapper): srp_auth = self.srpauth uuid = srp_auth.get_uid() - prefix = os.path.join(self._soledad_config.get_path_prefix(), - "leap", "soledad") + prefix = os.path.join(get_path_prefix(), "leap", "soledad") secrets_path = "%s/%s.secret" % (prefix, uuid) local_db_path = "%s/%s.db" % (prefix, uuid) @@ -186,11 +187,9 @@ class SoledadBootstrapper(AbstractBootstrapper): headers = {} mtime = get_mtime( - os.path.join( - self._soledad_config.get_path_prefix(), - "leap", "providers", - self._provider_config.get_domain(), - "soledad-service.json")) + os.path.join(get_path_prefix(), "leap", "providers", + self._provider_config.get_domain(), + "soledad-service.json")) if self._download_if_needed and mtime: headers['if-modified-since'] = mtime @@ -256,8 +255,8 @@ class SoledadBootstrapper(AbstractBootstrapper): # TODO: Fix for Windows gpgbin = "/usr/bin/gpg" - if self._standalone: - gpgbin = os.path.join(self._provider_config.get_path_prefix(), + if flags.STANDALONE: + gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail", "gpg") self._keymanager = KeyManager( @@ -284,8 +283,7 @@ class SoledadBootstrapper(AbstractBootstrapper): provider_config, user, password, - download_if_needed=False, - standalone=False): + download_if_needed=False): """ Starts the checks needed for a new soledad setup @@ -299,9 +297,6 @@ class SoledadBootstrapper(AbstractBootstrapper): files if the have changed since the time it was previously downloaded. :type download_if_needed: bool - :param standalone: If True, it'll look for paths inside the - bundle (like for gpg) - :type standalone: bool """ leap_assert_type(provider_config, ProviderConfig) @@ -310,7 +305,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed self._user = user self._password = password - self._standalone = standalone cb_chain = [ (self._download_config, self.download_config), diff --git a/src/leap/bitmask/services/soledad/soledadconfig.py b/src/leap/bitmask/services/soledad/soledadconfig.py index 7ed21f77..d3cc7da4 100644 --- a/src/leap/bitmask/services/soledad/soledadconfig.py +++ b/src/leap/bitmask/services/soledad/soledadconfig.py @@ -14,25 +14,25 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Soledad configuration """ import logging +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.soledad.soledadspec import get_schema -from leap.common.config.baseconfig import BaseConfig logger = logging.getLogger(__name__) -class SoledadConfig(BaseConfig): +class SoledadConfig(ServiceConfig): """ Soledad configuration abstraction class """ + _service_name = "soledad" def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) def _get_schema(self): """ diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index 78efcb6e..f762a350 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -20,6 +20,13 @@ Some small and handy functions. import datetime import os +from leap.bitmask.config import flags +from leap.common.config import get_path_prefix as common_get_path_prefix + + +def get_path_prefix(): + return common_get_path_prefix(flags.STANDALONE) + def first(things): """ diff --git a/src/leap/bitmask/util/averages.py b/src/leap/bitmask/util/averages.py new file mode 100644 index 00000000..65953f8f --- /dev/null +++ b/src/leap/bitmask/util/averages.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# averages.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Utility class for moving averages. + +It is used in the status panel widget for displaying up and down +download rates. +""" +from leap.bitmask.util import first + + +class RateMovingAverage(object): + """ + Moving window average for calculating + upload and download rates. + """ + SAMPLE_SIZE = 5 + + def __init__(self): + """ + Initializes an empty array of fixed size + """ + self.reset() + + def reset(self): + self._data = [None for i in xrange(self.SAMPLE_SIZE)] + + def append(self, x): + """ + Appends a new data point to the collection. + + :param x: A tuple containing timestamp and traffic points + in the form (timestamp, traffic) + :type x: tuple + """ + self._data.pop(0) + self._data.append(x) + + def get(self): + """ + Gets the collection. + """ + return self._data + + def get_average(self): + """ + Gets the moving average. + """ + data = filter(None, self.get()) + traff = [traffic for (ts, traffic) in data] + times = [ts for (ts, traffic) in data] + + try: + deltatraffic = traff[-1] - first(traff) + deltat = (times[-1] - first(times)).seconds + except IndexError: + deltatraffic = 0 + deltat = 0 + + try: + rate = float(deltatraffic) / float(deltat) / 1024 + except ZeroDivisionError: + rate = 0 + + # In some cases we get negative rates + if rate < 0: + rate = 0 + + return rate + + def get_total(self): + """ + Gets the total accumulated throughput. + """ + try: + return self._data[-1][1] / 1024 + except TypeError: + return 0 diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index 8f354f28..4b3eb57f 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -14,16 +14,21 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Keyring helpers. """ +import logging import keyring +from keyring.backends.file import EncryptedKeyring, PlaintextKeyring + +logger = logging.getLogger(__name__) + + OBSOLETE_KEYRINGS = [ - keyring.backends.file.EncryptedKeyring, - keyring.backends.file.PlaintextKeyring + EncryptedKeyring, + PlaintextKeyring ] @@ -34,4 +39,10 @@ def has_keyring(): :rtype: bool """ kr = keyring.get_keyring() - return kr is not None and kr.__class__ not in OBSOLETE_KEYRINGS + klass = kr.__class__ + logger.debug("Selected keyring: %s" % (klass,)) + + canuse = kr is not None and klass not in OBSOLETE_KEYRINGS + if not canuse: + logger.debug("Not using this keyring since it is obsolete") + return canuse diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index bc21a9cf..afe5be48 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -27,7 +27,7 @@ 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-2013 The LEAP Encryption Access Project" parser = argparse.ArgumentParser(description=""" Launches Bitmask""", epilog=epilog) parser.add_argument('-d', '--debug', action="store_true", @@ -50,6 +50,8 @@ Launches Bitmask""", epilog=epilog) help='Makes Bitmask use standalone' 'directories for configuration and binary' 'searching') + parser.add_argument('-V', '--version', action="store_true", + help='Displays Bitmask version and exits') # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/util/log_silencer.py index 09aa2cff..b9f69ad2 100644 --- a/src/leap/bitmask/util/log_silencer.py +++ b/src/leap/bitmask/util/log_silencer.py @@ -21,7 +21,7 @@ import logging import os import re -from leap.common.config import get_path_prefix +from leap.bitmask.util import get_path_prefix class SelectiveSilencerFilter(logging.Filter): @@ -48,12 +48,11 @@ class SelectiveSilencerFilter(logging.Filter): 'leap.common.events', ) - def __init__(self, standalone=False): + def __init__(self): """ Tries to load silencer rules from the default path, or load from the SILENCER_RULES tuple if not found. """ - self.standalone = standalone self.rules = None if os.path.isfile(self._rules_path): self.rules = self._load_rules() @@ -65,9 +64,7 @@ class SelectiveSilencerFilter(logging.Filter): """ The configuration file for custom ignore rules. """ - return os.path.join( - get_path_prefix(standalone=self.standalone), - "leap", self.CONFIG_NAME) + return os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) def _load_rules(self): """ |