summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali <kali@leap.se>2014-08-08 19:10:39 -0500
committerkali <kali@leap.se>2014-08-08 19:10:39 -0500
commitcef0be0b4f866c9283eaf696fbc23b8120e40443 (patch)
treeff54448f7354e56235d22dabaf0fb03597790c98
parenta9173e8e2816b767028787735e4a84b2576f258d (diff)
parent465c0b89ca61279eb036afec2de3b4d24b257311 (diff)
Merge branch '0.6.1-rc' into deb-0.6.1-rc
Conflicts: pkg/linux/bitmask-root src/leap/bitmask/gui/login.py
-rw-r--r--CHANGELOG.rst17
-rw-r--r--changes/4035_add-eip-cancel-button1
-rw-r--r--changes/5870_bitmask-prevent-system-logout-on-linux1
-rw-r--r--changes/add-backend-alive-check1
-rw-r--r--changes/add-frontend-alive-check2
-rw-r--r--changes/add-linux-autostart1
-rw-r--r--changes/bug-5949_pastebin-does-not-work1
-rw-r--r--changes/bug_5955_fix-vpnlauncher-error-ui1
-rw-r--r--changes/bug_logger-hangs-quit1
-rw-r--r--changes/bug_use-smaller-window-to-fit-lower-resolutions1
-rw-r--r--changes/enable-child-process-in-backend1
-rw-r--r--changes/feature_restrict-certificates-permissions1
-rw-r--r--[-rwxr-xr-x]pkg/linux/bitmask-root15
-rw-r--r--pkg/osx/Info.plist34
-rw-r--r--pkg/osx/bitmask.icnsbin0 -> 47303 bytes
-rw-r--r--pkg/requirements.pip2
-rwxr-xr-xpkg/tuf/init.py102
-rwxr-xr-xpkg/tuf/release.py114
-rw-r--r--relnotes.txt18
-rwxr-xr-xsetup.py31
-rw-r--r--src/leap/bitmask/__init__.py14
-rw-r--r--src/leap/bitmask/app.py130
-rw-r--r--src/leap/bitmask/backend/api.py154
-rw-r--r--src/leap/bitmask/backend/backend.py246
-rw-r--r--src/leap/bitmask/backend/backend_proxy.py209
-rw-r--r--src/leap/bitmask/backend/components.py144
-rw-r--r--src/leap/bitmask/backend/leapbackend.py305
-rw-r--r--src/leap/bitmask/backend/leapsignaler.py435
-rw-r--r--src/leap/bitmask/backend/settings.py158
-rw-r--r--src/leap/bitmask/backend/signaler.py177
-rw-r--r--src/leap/bitmask/backend/signaler_qt.py136
-rw-r--r--src/leap/bitmask/backend/utils.py64
-rw-r--r--src/leap/bitmask/backend_app.py64
-rw-r--r--src/leap/bitmask/config/flags.py2
-rw-r--r--src/leap/bitmask/config/provider_spec.py2
-rw-r--r--src/leap/bitmask/crypto/srpauth.py26
-rw-r--r--src/leap/bitmask/crypto/srpregister.py6
-rwxr-xr-xsrc/leap/bitmask/crypto/tests/fake_provider.py4
-rw-r--r--src/leap/bitmask/frontend_app.py116
-rw-r--r--src/leap/bitmask/gui/__init__.py7
-rw-r--r--src/leap/bitmask/gui/eip_preferenceswindow.py11
-rw-r--r--src/leap/bitmask/gui/eip_status.py88
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py117
-rw-r--r--src/leap/bitmask/gui/login.py3
-rw-r--r--src/leap/bitmask/gui/mail_status.py2
-rw-r--r--src/leap/bitmask/gui/mainwindow.py304
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py13
-rw-r--r--src/leap/bitmask/gui/statemachines.py11
-rw-r--r--src/leap/bitmask/gui/twisted_main.py43
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui41
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui6
-rw-r--r--src/leap/bitmask/gui/wizard.py115
-rw-r--r--src/leap/bitmask/platform_init/__init__.py6
-rw-r--r--src/leap/bitmask/platform_init/initializers.py3
-rw-r--r--src/leap/bitmask/provider/pinned.py10
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py22
-rw-r--r--src/leap/bitmask/services/eip/conductor.py40
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py4
-rw-r--r--src/leap/bitmask/services/eip/eipbootstrapper.py6
-rw-r--r--src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py2
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py24
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py50
-rw-r--r--src/leap/bitmask/services/mail/conductor.py6
-rw-r--r--src/leap/bitmask/services/mail/plumber.py11
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py44
-rw-r--r--src/leap/bitmask/services/tests/test_abstractbootstrapper.py2
-rw-r--r--src/leap/bitmask/util/__init__.py25
-rw-r--r--src/leap/bitmask/util/autostart.py70
-rw-r--r--src/leap/bitmask/util/keyring_helpers.py7
-rw-r--r--src/leap/bitmask/util/leap_argparse.py26
-rw-r--r--src/leap/bitmask/util/polkit_agent.py6
-rw-r--r--src/leap/bitmask/util/privilege_policies.py4
72 files changed, 2720 insertions, 1146 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 92da1573..e8d24c95 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,23 @@ History
2014
====
+0.6.0 July 18 -- the "nothing to see here" release:
++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+- Initial sync message is confusing. Closes #5875.
+- Use preferred provider on first run. Closes #5813.
+- Add TUF init repository and release tools. Closes #5864.
+- Add support for fingerprint-gui's polkit agent. Closes #5880.
+- Reroute DNS packets instead of blocking them, eliminating need to
+ muck around with resolv.conf. Closes #4633, #5655, #5738, #4823
+- Use inline decrypting for initial soledad syncrhonization, to wait
+ for secrets.
+- Add the ability to create an osx bundle with py2app. Closes #5845.
+- Split frontend/backend in different files. Closes #5719.
+- Implement ZMQ based messaging system. Closes #5733.
+- Launch the backend in a different process than the app. Closes
+ #5734.
+
0.5.3 June 27 -- the "encrypt ALL THE THINGS" release:
++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/changes/4035_add-eip-cancel-button b/changes/4035_add-eip-cancel-button
new file mode 100644
index 00000000..ea63f8a4
--- /dev/null
+++ b/changes/4035_add-eip-cancel-button
@@ -0,0 +1 @@
+- Add a button for cancelling ongoing EIP connection. Closes: #4035
diff --git a/changes/5870_bitmask-prevent-system-logout-on-linux b/changes/5870_bitmask-prevent-system-logout-on-linux
new file mode 100644
index 00000000..6feba46c
--- /dev/null
+++ b/changes/5870_bitmask-prevent-system-logout-on-linux
@@ -0,0 +1 @@
+- Bitmask on linux prevents session logout. Closes #5870.
diff --git a/changes/add-backend-alive-check b/changes/add-backend-alive-check
new file mode 100644
index 00000000..40e12978
--- /dev/null
+++ b/changes/add-backend-alive-check
@@ -0,0 +1 @@
+- Add checks to ensure that the backend is alive or notify the user. Related to #5873.
diff --git a/changes/add-frontend-alive-check b/changes/add-frontend-alive-check
new file mode 100644
index 00000000..54f88798
--- /dev/null
+++ b/changes/add-frontend-alive-check
@@ -0,0 +1,2 @@
+- Stop the backend if the frontend process does not exist any more and backend
+ is not a daemon. Related to #5873.
diff --git a/changes/add-linux-autostart b/changes/add-linux-autostart
new file mode 100644
index 00000000..44b175bc
--- /dev/null
+++ b/changes/add-linux-autostart
@@ -0,0 +1 @@
+- Add autostart on Linux. Closes #4989.
diff --git a/changes/bug-5949_pastebin-does-not-work b/changes/bug-5949_pastebin-does-not-work
new file mode 100644
index 00000000..aa983184
--- /dev/null
+++ b/changes/bug-5949_pastebin-does-not-work
@@ -0,0 +1 @@
+- Replace twisted thread with QThread and get pastebin send working. Closes #5949.
diff --git a/changes/bug_5955_fix-vpnlauncher-error-ui b/changes/bug_5955_fix-vpnlauncher-error-ui
new file mode 100644
index 00000000..d322288e
--- /dev/null
+++ b/changes/bug_5955_fix-vpnlauncher-error-ui
@@ -0,0 +1 @@
+- Fix the handling of vpn launcher errors in the UI. Closes: #5955
diff --git a/changes/bug_logger-hangs-quit b/changes/bug_logger-hangs-quit
new file mode 100644
index 00000000..b76f6218
--- /dev/null
+++ b/changes/bug_logger-hangs-quit
@@ -0,0 +1 @@
+- Fix logger window blocking the bitmask quit().
diff --git a/changes/bug_use-smaller-window-to-fit-lower-resolutions b/changes/bug_use-smaller-window-to-fit-lower-resolutions
new file mode 100644
index 00000000..d9df69ae
--- /dev/null
+++ b/changes/bug_use-smaller-window-to-fit-lower-resolutions
@@ -0,0 +1 @@
+- Use smaller height on the window so it fits better on smaller resolutions. Closes #5722.
diff --git a/changes/enable-child-process-in-backend b/changes/enable-child-process-in-backend
new file mode 100644
index 00000000..1b6f246e
--- /dev/null
+++ b/changes/enable-child-process-in-backend
@@ -0,0 +1 @@
+- Disable daemon mode when we run the backend so we can spawn child processes on it.
diff --git a/changes/feature_restrict-certificates-permissions b/changes/feature_restrict-certificates-permissions
new file mode 100644
index 00000000..6bd6c015
--- /dev/null
+++ b/changes/feature_restrict-certificates-permissions
@@ -0,0 +1 @@
+- Restrict access to the zmq certificates folder.
diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root
index 56202b5f..c9034b0d 100755..100644
--- a/pkg/linux/bitmask-root
+++ b/pkg/linux/bitmask-root
@@ -34,7 +34,6 @@ not be teared down in the case of an error during launch.
"""
# TODO should be tested with python3, which can be the default on some distro.
from __future__ import print_function
-import atexit
import os
import re
import signal
@@ -42,7 +41,6 @@ import socket
import syslog
import subprocess
import sys
-import time
import traceback
cmdcheck = subprocess.check_output
@@ -51,7 +49,7 @@ cmdcheck = subprocess.check_output
# CONSTANTS
#
-VERSION = "1"
+VERSION = "2"
SCRIPT = "bitmask-root"
NAMESERVER = "10.42.0.1"
BITMASK_CHAIN = "bitmask"
@@ -659,17 +657,6 @@ def firewall_start(args):
ip4tables("--append", BITMASK_CHAIN, "-o",
default_device, "--jump", "REJECT")
- # workaround for ipv6 servers being blocked and not falling back to ipv4.
- # See #5693
- ip6tables("--append", "OUTPUT", "--jump", "REJECT",
- "-s", "::/0", "-d", "::/0",
- "-p", "tcp",
- "--reject-with", "icmp6-port-unreachable")
- ip6tables("--append", "OUTPUT", "--jump", "REJECT",
- "-s", "::/0", "-d", "::/0",
- "-p", "udp",
- "--reject-with", "icmp6-port-unreachable")
-
def firewall_stop():
"""
diff --git a/pkg/osx/Info.plist b/pkg/osx/Info.plist
index e90d920a..dc427c4a 100644
--- a/pkg/osx/Info.plist
+++ b/pkg/osx/Info.plist
@@ -2,21 +2,23 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
- <key>CFBundleDisplayName</key>
- <string>leap-client</string>
- <key>CFBundleExecutable</key>
- <string>MacOS/app</string>
- <key>CFBundleIconFile</key>
- <string>icon-windowed.icns</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundleName</key>
- <string>leap-client</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleShortVersionString</key>
- <string>1</string>
- <key>LSBackgroundOnly</key>
- <false/>
+ <key>CFBundleDisplayName</key>
+ <string>Bitmask</string>
+ <key>CFBundleExecutable</key>
+ <string>app</string>
+ <key>CFBundleIconFile</key>
+ <string>bitmask.icns</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Bitmask</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1</string>
+ <key>LSBackgroundOnly</key>
+ <false/>
+ <key>CFBundleIdentifier</key>
+ <string>se.leap.bitmask</string>
</dict>
</plist>
diff --git a/pkg/osx/bitmask.icns b/pkg/osx/bitmask.icns
new file mode 100644
index 00000000..7cc3e752
--- /dev/null
+++ b/pkg/osx/bitmask.icns
Binary files differ
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 3d6b33a3..bf05aa28 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -19,6 +19,8 @@ python-daemon # this should not be needed for Windows.
keyring
zope.proxy
+pyzmq
+
leap.common>=0.3.7
leap.soledad.client>=0.5.0
leap.keymanager>=0.3.8
diff --git a/pkg/tuf/init.py b/pkg/tuf/init.py
new file mode 100755
index 00000000..7300da0a
--- /dev/null
+++ b/pkg/tuf/init.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# init.py
+# Copyright (C) 2014 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/>.
+
+"""
+Tool to initialize a TUF repo.
+
+The keys can be generated with:
+ openssl genrsa -des3 -out private.pem 4096
+The public key can be exported with:
+ openssl rsa -in private.pem -outform PEM -pubout -out public.pem
+"""
+
+import sys
+
+from tuf.repository_tool import create_new_repository
+from tuf.repository_tool import import_rsa_privatekey_from_file
+from tuf.repository_tool import import_rsa_publickey_from_file
+
+
+def usage():
+ print ("Usage: %s repo root_private_key root_pub_key targets_pub_key"
+ " timestamp_pub_key") % (sys.argv[0],)
+
+
+def main():
+ if len(sys.argv) < 6:
+ usage()
+ return
+
+ repo_path = sys.argv[1]
+ root_priv_path = sys.argv[2]
+ root_pub_path = sys.argv[3]
+ targets_pub_path = sys.argv[4]
+ timestamp_pub_path = sys.argv[5]
+ repo = Repo(repo_path, root_priv_path)
+ repo.build(root_pub_path, targets_pub_path, timestamp_pub_path)
+
+ print "%s/metadata.staged/root.json is ready" % (repo_path,)
+
+
+class Repo(object):
+ """
+ Repository builder class
+ """
+
+ def __init__(self, repo_path, key_path):
+ """
+ Constructor
+
+ :param repo_path: path where the repo lives
+ :type repo_path: str
+ :param key_path: path where the private root key lives
+ :type key_path: str
+ """
+ self._repo_path = repo_path
+ self._key = import_rsa_privatekey_from_file(key_path)
+
+ def build(self, root_pub_path, targets_pub_path, timestamp_pub_path):
+ """
+ Create a new repo
+
+ :param root_pub_path: path where the public root key lives
+ :type root_pub_path: str
+ :param targets_pub_path: path where the public targets key lives
+ :type targets_pub_path: str
+ :param timestamp_pub_path: path where the public timestamp key lives
+ :type timestamp_pub_path: str
+ """
+ repository = create_new_repository(self._repo_path)
+
+ pub_root_key = import_rsa_publickey_from_file(root_pub_path)
+ repository.root.add_verification_key(pub_root_key)
+ repository.root.load_signing_key(self._key)
+
+ pub_target_key = import_rsa_publickey_from_file(targets_pub_path)
+ repository.targets.add_verification_key(pub_target_key)
+ repository.snapshot.add_verification_key(pub_target_key)
+ repository.targets.compressions = ["gz"]
+ repository.snapshot.compressions = ["gz"]
+
+ pub_timestamp_key = import_rsa_publickey_from_file(timestamp_pub_path)
+ repository.timestamp.add_verification_key(pub_timestamp_key)
+
+ repository.write_partial()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pkg/tuf/release.py b/pkg/tuf/release.py
new file mode 100755
index 00000000..c4abcd0d
--- /dev/null
+++ b/pkg/tuf/release.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# release.py
+# Copyright (C) 2014 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/>.
+
+"""
+Tool to generate TUF related files after a release
+
+The 'repo' folder should contain two folders:
+ - 'metadata.staged' with all the jsons from the previows release
+ - 'targets' where the release targets are
+"""
+
+import datetime
+import os.path
+import sys
+
+from tuf.repository_tool import load_repository
+from tuf.repository_tool import import_rsa_privatekey_from_file
+from tuf.repository_tool import import_rsa_publickey_from_file
+
+"""
+Days until the expiration of targets.json and snapshot.json. After this ammount
+of days the TUF client won't accept this files.
+"""
+EXPIRATION_DAYS = 90
+
+
+def usage():
+ print "Usage: %s repo key" % (sys.argv[0],)
+
+
+def main():
+ if len(sys.argv) < 3:
+ usage()
+ return
+
+ repo_path = sys.argv[1]
+ key_path = sys.argv[2]
+ targets = Targets(repo_path, key_path)
+ targets.build()
+
+ print "%s/metadata.staged/(targets|snapshot).json[.gz] are ready" % \
+ (repo_path,)
+
+
+class Targets(object):
+ """
+ Targets builder class
+ """
+
+ def __init__(self, repo_path, key_path):
+ """
+ Constructor
+
+ :param repo_path: path where the repo lives
+ :type repo_path: str
+ :param key_path: path where the private targets key lives
+ :type key_path: str
+ """
+ self._repo_path = repo_path
+ self._key = import_rsa_privatekey_from_file(key_path)
+
+ def build(self):
+ """
+ Generate snapshot.json[.gz] and targets.json[.gz]
+ """
+ self._repo = load_repository(self._repo_path)
+ self._load_targets()
+
+ self._repo.targets.load_signing_key(self._key)
+ self._repo.snapshot.load_signing_key(self._key)
+ self._repo.targets.compressions = ["gz"]
+ self._repo.snapshot.compressions = ["gz"]
+ self._repo.snapshot.expiration = (
+ datetime.datetime.now() +
+ datetime.timedelta(days=EXPIRATION_DAYS))
+ self._repo.targets.expiration = (
+ datetime.datetime.now() +
+ datetime.timedelta(days=EXPIRATION_DAYS))
+ self._repo.write_partial()
+
+ def _load_targets(self):
+ """
+ Load a list of targets
+ """
+ targets_path = os.path.join(self._repo_path, 'targets')
+ target_list = self._repo.get_filepaths_in_directory(
+ targets_path,
+ recursive_walk=True,
+ followlinks=True)
+
+ for target in target_list:
+ octal_file_permissions = oct(os.stat(target).st_mode)[3:]
+ custom_file_permissions = {
+ 'file_permissions': octal_file_permissions
+ }
+ self._repo.targets.add_target(target, custom_file_permissions)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/relnotes.txt b/relnotes.txt
index 4fc33b39..925db87d 100644
--- a/relnotes.txt
+++ b/relnotes.txt
@@ -1,8 +1,8 @@
-ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.3
+ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.6.0
The LEAP team is pleased to announce the immediate availability of
-version 0.5.3 of Bitmask, the Internet Encryption Toolkit, codename
-"encrypt ALL THE THINGS".
+version 0.6.0 of Bitmask, the Internet Encryption Toolkit, codename
+"nothing to see here".
https://downloads.leap.se/client/
@@ -21,7 +21,7 @@ WARNING (LINUX ONLY): If you ever run into the situation where you
cannot access internet, open the terminal and run the following
command:
-$ pkexec /usr/sbin/bitmask-root firewall stop
+$ pkexec /usr/local/sbin/bitmask-root firewall stop
If for some reason that doesn't work, you will need to reboot your
computer.
@@ -43,9 +43,11 @@ NOT trust your life to it.
WHAT CAN THIS VERSION OF BITMASK DO FOR ME?
-Bitmask 0.5.3 improves greatly its Encrypted Internet support and
-stability in general, among other various bug fixes. You can refer to
-the CHANGELOG for the meat.
+Bitmask 0.6.0 improves how email encryption/decryption works
+internally to improve speed and CPU consumption. This release also
+merges a big refactor we've been working on for some time, which will
+give us room for a lot of improvements in the near future. You can
+refer to the CHANGELOG for the meat.
Encrypted Internet on Linux now helps you don't shoot yourself in the
foot by leaking traffic outside of the secure connection it
@@ -104,6 +106,6 @@ beyond any border.
The LEAP team,
-June 27, 2014
+July 18, 2014
Somewhere in the middle of the intertubes.
EOF
diff --git a/setup.py b/setup.py
index bb1937cc..bfd7eff0 100755
--- a/setup.py
+++ b/setup.py
@@ -137,7 +137,7 @@ def get_versions(default={}, verbose=False):
cmdclass["freeze_debianver"] = freeze_debianver
parsed_reqs = utils.parse_requirements()
-leap_launcher = 'bitmask=leap.bitmask.app:main'
+leap_launcher = 'bitmask=leap.bitmask.app:start_app'
from setuptools.command.develop import develop as _develop
@@ -253,7 +253,8 @@ cmdclass["sdist"] = cmd_sdist
import platform
_system = platform.system()
-IS_LINUX = True if _system == "Linux" else False
+IS_LINUX = _system == "Linux"
+IS_MAC = _system == "Darwin"
data_files = []
@@ -267,6 +268,31 @@ if IS_LINUX:
["pkg/linux/bitmask-root"]),
]
+extra_options = {}
+
+if IS_MAC:
+ extra_options["app"] = ['src/leap/bitmask/app.py']
+ OPTIONS = {
+ 'argv_emulation': True,
+ 'plist': 'pkg/osx/Info.plist',
+ 'iconfile': 'pkg/osx/bitmask.icns',
+ }
+ extra_options["options"] = {'py2app': OPTIONS}
+ extra_options["setup_requires"] = ['py2app']
+
+ class jsonschema_recipe(object):
+ def check(self, dist, mf):
+ m = mf.findNode('jsonschema')
+ if m is None:
+ return None
+
+ # Don't put jsonschema in the site-packages.zip file
+ return dict(
+ packages=['jsonschema']
+ )
+
+ import py2app.recipes
+ py2app.recipes.jsonschema = jsonschema_recipe()
setup(
name="leap.bitmask",
@@ -305,4 +331,5 @@ setup(
entry_points={
'console_scripts': [leap_launcher]
},
+ **extra_options
)
diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py
index 0f733f26..9ec5aae7 100644
--- a/src/leap/bitmask/__init__.py
+++ b/src/leap/bitmask/__init__.py
@@ -25,6 +25,12 @@ from pkg_resources import parse_version
from leap.bitmask.util import first
+# HACK: This is a hack so that py2app copies _scrypt.so to the right
+# place, it can't be technically imported, but that doesn't matter
+# because the import is never executed
+if False:
+ import _scrypt # noqa - skip 'not used' warning
+
def _is_release_version(version):
"""
@@ -60,16 +66,16 @@ try:
IS_RELEASE_VERSION = _is_release_version(__version__)
del get_versions
except ImportError:
- #running on a tree that has not run
- #the setup.py setver
+ # running on a tree that has not run
+ # the setup.py setver
pass
__appname__ = "unknown"
try:
from leap.bitmask._appname import __appname__
except ImportError:
- #running on a tree that has not run
- #the setup.py setver
+ # running on a tree that has not run
+ # the setup.py setver
pass
__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__))
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 6a7d6ff1..ad886bc4 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -39,56 +39,49 @@
# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M
# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M
# (thanks to: http://www.glassgiant.com/ascii/)
-import signal
-import sys
+import multiprocessing
import os
+import sys
-from functools import partial
-from PySide import QtCore, QtGui
+from leap.bitmask.backend.utils import generate_certificates
from leap.bitmask import __version__ as VERSION
from leap.bitmask.config import flags
-from leap.bitmask.gui import locale_rc # noqa - silence pylint
-from leap.bitmask.gui.mainwindow import MainWindow
+from leap.bitmask.frontend_app import run_frontend
+from leap.bitmask.backend_app import run_backend
from leap.bitmask.logs.utils import create_logger
from leap.bitmask.platform_init.locks import we_are_the_one_and_only
from leap.bitmask.services.mail import plumber
-from leap.bitmask.util import leap_argparse
+from leap.bitmask.util import leap_argparse, flags_to_dict
from leap.bitmask.util.requirement_checker import check_requirements
from leap.common.events import server as event_server
from leap.mail import __version__ as MAIL_VERSION
-from twisted.internet import reactor
-from twisted.internet.task import LoopingCall
-
import codecs
codecs.register(lambda name: codecs.lookup('utf-8')
if name == 'cp65001' else None)
-
-def sigint_handler(*args, **kwargs):
- """
- Signal handler for SIGINT
- """
- logger = kwargs.get('logger', None)
- if logger:
- logger.debug("SIGINT catched. shutting down...")
- mainwindow = args[0]
- mainwindow.quit()
+import psutil
-def sigterm_handler(*args, **kwargs):
+def kill_the_children():
"""
- Signal handler for SIGTERM.
- This handler is actually passed to twisted reactor
+ Make sure no lingering subprocesses are left in case of a bad termination.
"""
- logger = kwargs.get('logger', None)
- if logger:
- logger.debug("SIGTERM catched. shutting down...")
- mainwindow = args[0]
- mainwindow.quit()
+ me = os.getpid()
+ parent = psutil.Process(me)
+ print "Killing all the children processes..."
+ for child in parent.get_children(recursive=True):
+ try:
+ child.terminate()
+ except Exception as exc:
+ print exc
+
+# XXX This is currently broken, but we need to fix it to avoid
+# orphaned processes in case of a crash.
+# atexit.register(kill_the_children)
def do_display_version(opts):
@@ -116,16 +109,22 @@ def do_mail_plumbing(opts):
# XXX catch when import is used w/o acct
-def main():
+def start_app():
"""
Starts the main event loop and launches the main window.
"""
+ # Ignore the signals since we handle them in the subprocesses
+ # signal.signal(signal.SIGINT, signal.SIG_IGN)
+
# Parse arguments and store them
opts = leap_argparse.get_options()
do_display_version(opts)
- bypass_checks = opts.danger
- start_hidden = opts.start_hidden
+ options = {
+ 'start_hidden': opts.start_hidden,
+ 'debug': opts.debug,
+ 'log_file': opts.log_file,
+ }
flags.STANDALONE = opts.standalone
flags.OFFLINE = opts.offline
@@ -177,59 +176,20 @@ def main():
logger.info('Starting app')
- # 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
- if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None:
- sys.argv.append("-style")
- sys.argv.append("Cleanlooks")
-
- app = QtGui.QApplication(sys.argv)
-
- # To test:
- # $ LANG=es ./app.py
- locale = QtCore.QLocale.system().name()
- qtTranslator = QtCore.QTranslator()
- if qtTranslator.load("qt_%s" % locale, ":/translations"):
- app.installTranslator(qtTranslator)
- appTranslator = QtCore.QTranslator()
- if appTranslator.load("%s.qm" % locale[:2], ":/translations"):
- app.installTranslator(appTranslator)
-
- # Needed for initializing qsettings it will write
- # .config/leap/leap.conf top level app settings in a platform
- # independent way
- app.setOrganizationName("leap")
- app.setApplicationName("leap")
- app.setOrganizationDomain("leap.se")
-
- # XXX ---------------------------------------------------------
- # In quarantine, looks like we don't need it anymore.
- # This dummy timer ensures that control is given to the outside
- # loop, so we can hook our sigint handler.
- #timer = QtCore.QTimer()
- #timer.start(500)
- #timer.timeout.connect(lambda: None)
- # XXX ---------------------------------------------------------
-
- window = MainWindow(bypass_checks=bypass_checks,
- start_hidden=start_hidden)
-
- sigint_window = partial(sigint_handler, window, logger=logger)
- signal.signal(signal.SIGINT, sigint_window)
-
- # callable used in addSystemEventTrigger to handle SIGTERM
- sigterm_window = partial(sigterm_handler, window, logger=logger)
-
- l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10)
- l.start(0.01)
-
- # SIGTERM can't be handled the same way SIGINT is, since it's
- # caught by twisted. See _handleSignals method in
- # twisted/internet/base.py#L1150. So, addSystemEventTrigger
- # reactor's method is used.
- reactor.addSystemEventTrigger('before', 'shutdown', sigterm_window)
- reactor.run()
+ generate_certificates()
+
+ flags_dict = flags_to_dict()
+
+ frontend_pid = os.getpid()
+ backend = lambda: run_backend(opts.danger, flags_dict, frontend_pid)
+ backend_process = multiprocessing.Process(target=backend, name='Backend')
+ # we don't set the 'daemon mode' since we need to start child processes in
+ # the backend
+ # backend_process.daemon = True
+ backend_process.start()
+
+ run_frontend(options, flags_dict, backend_pid=backend_process.pid)
+
if __name__ == "__main__":
- main()
+ start_app()
diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py
new file mode 100644
index 00000000..3f6c0ad1
--- /dev/null
+++ b/src/leap/bitmask/backend/api.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+# api.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Backend available API and SIGNALS definition.
+"""
+STOP_REQUEST = "stop"
+PING_REQUEST = "PING"
+
+# XXX this needs documentation. What is it used for?
+
+API = (
+ STOP_REQUEST, # this method needs to be defined in order to support the
+ # backend stop action
+ PING_REQUEST,
+
+ "eip_can_start",
+ "eip_cancel_setup",
+ "eip_check_dns",
+ "eip_get_gateway_country_code",
+ "eip_get_gateways_list",
+ "eip_get_initialized_providers",
+ "eip_setup",
+ "eip_start",
+ "eip_stop",
+ "eip_terminate",
+ "imap_start_service",
+ "imap_stop_service",
+ "keymanager_export_keys",
+ "keymanager_get_key_details",
+ "keymanager_list_keys",
+ "provider_bootstrap",
+ "provider_cancel_setup",
+ "provider_get_all_services",
+ "provider_get_details",
+ "provider_get_pinned_providers",
+ "provider_get_supported_services",
+ "provider_setup",
+ "settings_set_selected_gateway",
+ "smtp_start_service",
+ "smtp_stop_service",
+ "soledad_bootstrap",
+ "soledad_cancel_bootstrap",
+ "soledad_change_password",
+ "soledad_close",
+ "soledad_load_offline",
+ "tear_fw_down",
+ "bitmask_root_vpn_down",
+ "user_cancel_login",
+ "user_change_password",
+ "user_get_logged_in_status",
+ "user_login",
+ "user_logout",
+ "user_register",
+)
+
+
+SIGNALS = (
+ "backend_bad_call",
+ "eip_alien_openvpn_already_running",
+ "eip_can_start",
+ "eip_cancelled_setup",
+ "eip_cannot_start",
+ "eip_client_certificate_ready",
+ "eip_config_ready",
+ "eip_connected",
+ "eip_connection_aborted",
+ "eip_connection_died",
+ "eip_disconnected",
+ "eip_dns_error",
+ "eip_dns_ok",
+ "eip_get_gateway_country_code",
+ "eip_get_gateways_list",
+ "eip_get_gateways_list_error",
+ "eip_get_initialized_providers",
+ "eip_network_unreachable",
+ "eip_no_gateway",
+ "eip_no_pkexec_error",
+ "eip_no_polkit_agent_error",
+ "eip_no_tun_kext_error",
+ "eip_openvpn_already_running",
+ "eip_openvpn_not_found_error",
+ "eip_process_finished",
+ "eip_process_restart_ping",
+ "eip_process_restart_tls",
+ "eip_state_changed",
+ "eip_status_changed",
+ "eip_stopped",
+ "eip_tear_fw_down",
+ "eip_bitmask_root_vpn_down",
+ "eip_uninitialized_provider",
+ "eip_vpn_launcher_exception",
+ "imap_stopped",
+ "keymanager_export_error",
+ "keymanager_export_ok",
+ "keymanager_import_addressmismatch",
+ "keymanager_import_datamismatch",
+ "keymanager_import_ioerror",
+ "keymanager_import_missingkey",
+ "keymanager_import_ok",
+ "keymanager_key_details",
+ "keymanager_keys_list",
+ "prov_cancelled_setup",
+ "prov_check_api_certificate",
+ "prov_check_ca_fingerprint",
+ "prov_download_ca_cert",
+ "prov_download_provider_info",
+ "prov_get_all_services",
+ "prov_get_details",
+ "prov_get_pinned_providers",
+ "prov_get_supported_services",
+ "prov_https_connection",
+ "prov_name_resolution",
+ "prov_problem_with_provider",
+ "prov_unsupported_api",
+ "prov_unsupported_client",
+ "soledad_bootstrap_failed",
+ "soledad_bootstrap_finished",
+ "soledad_cancelled_bootstrap",
+ "soledad_invalid_auth_token",
+ "soledad_offline_failed",
+ "soledad_offline_finished",
+ "soledad_password_change_error",
+ "soledad_password_change_ok",
+ "srp_auth_bad_user_or_password",
+ "srp_auth_connection_error",
+ "srp_auth_error",
+ "srp_auth_ok",
+ "srp_auth_server_error",
+ "srp_logout_error",
+ "srp_logout_ok",
+ "srp_not_logged_in_error",
+ "srp_password_change_badpw",
+ "srp_password_change_error",
+ "srp_password_change_ok",
+ "srp_registration_failed",
+ "srp_registration_finished",
+ "srp_registration_taken",
+ "srp_status_logged_in",
+ "srp_status_not_logged_in",
+)
diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py
new file mode 100644
index 00000000..37535f37
--- /dev/null
+++ b/src/leap/bitmask/backend/backend.py
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+# backend.py
+# Copyright (C) 2013, 2014 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/>.
+
+# FIXME this is missing module documentation. It would be fine to say a couple
+# of lines about the whole backend architecture.
+# TODO use txzmq bindings instead.
+
+import json
+import threading
+import time
+
+import psutil
+
+from twisted.internet import defer, reactor, threads
+
+import zmq
+from zmq.auth.thread import ThreadAuthenticator
+
+from leap.bitmask.backend.api import API, PING_REQUEST
+from leap.bitmask.backend.utils import get_backend_certificates
+from leap.bitmask.backend.signaler import Signaler
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Backend(object):
+ """
+ Backend server.
+ Receives signals from backend_proxy and emit signals if needed.
+ """
+ # XXX this should not be hardcoded. Make it configurable.
+ PORT = '5556'
+
+ # XXX we might want to make this configurable per-platform,
+ # and use the most performant socket type on each one.
+ BIND_ADDR = "tcp://127.0.0.1:%s" % PORT
+
+ PING_INTERVAL = 2 # secs
+
+ def __init__(self, frontend_pid=None):
+ """
+ Backend constructor, create needed instances.
+ """
+ self._signaler = Signaler()
+
+ self._frontend_pid = frontend_pid
+
+ self._do_work = threading.Event() # used to stop the worker thread.
+ self._zmq_socket = None
+
+ self._ongoing_defers = []
+ self._init_zmq()
+
+ def _init_zmq(self):
+ """
+ Configure the zmq components and connection.
+ """
+ context = zmq.Context()
+ socket = context.socket(zmq.REP)
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(context)
+ auth.start()
+ # XXX do not hardcode this here.
+ auth.allow('127.0.0.1')
+
+ # Tell authenticator to use the certificate in a directory
+ auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
+ public, secret = get_backend_certificates()
+ socket.curve_publickey = public
+ socket.curve_secretkey = secret
+ socket.curve_server = True # must come before bind
+
+ socket.bind(self.BIND_ADDR)
+
+ self._zmq_socket = socket
+
+ def _worker(self):
+ """
+ Receive requests and send it to process.
+
+ Note: we use a simple while since is less resource consuming than a
+ Twisted's LoopingCall.
+ """
+ pid = self._frontend_pid
+ check_wait = 0
+ while self._do_work.is_set():
+ # Wait for next request from client
+ try:
+ request = self._zmq_socket.recv(zmq.NOBLOCK)
+ self._zmq_socket.send("OK")
+ # logger.debug("Received request: '{0}'".format(request))
+ self._process_request(request)
+ except zmq.ZMQError as e:
+ if e.errno != zmq.EAGAIN:
+ raise
+ time.sleep(0.01)
+
+ check_wait += 0.01
+ if pid is not None and check_wait > self.PING_INTERVAL:
+ check_wait = 0
+ self._check_frontend_alive()
+
+ def _check_frontend_alive(self):
+ """
+ Check if the frontend is alive and stop the backend if it is not.
+ """
+ pid = self._frontend_pid
+ if pid is not None and not psutil.pid_exists(pid):
+ logger.critical("The frontend is down!")
+ self.stop()
+
+ def _stop_reactor(self):
+ """
+ Stop the Twisted reactor, but first wait a little for some threads to
+ complete their work.
+
+ Note: this method needs to be run in a different thread so the
+ time.sleep() does not block and other threads can finish.
+ i.e.:
+ use threads.deferToThread(this_method) instead of this_method()
+ """
+ wait_max = 5 # seconds
+ wait_step = 0.5
+ wait = 0
+ while self._ongoing_defers and wait < wait_max:
+ time.sleep(wait_step)
+ wait += wait_step
+ msg = "Waiting for running threads to finish... {0}/{1}"
+ msg = msg.format(wait, wait_max)
+ logger.debug(msg)
+
+ # after a timeout we shut down the existing threads.
+ for d in self._ongoing_defers:
+ d.cancel()
+
+ reactor.stop()
+ logger.debug("Twisted reactor stopped.")
+
+ def run(self):
+ """
+ Start the ZMQ server and run the loop to handle requests.
+ """
+ self._signaler.start()
+ self._do_work.set()
+ threads.deferToThread(self._worker)
+ reactor.run()
+
+ def stop(self):
+ """
+ Stop the server and the zmq request parse loop.
+ """
+ logger.debug("STOP received.")
+ self._signaler.stop()
+ self._do_work.clear()
+ threads.deferToThread(self._stop_reactor)
+
+ def _process_request(self, request_json):
+ """
+ Process a request and call the according method with the given
+ parameters.
+
+ :param request_json: a json specification of a request.
+ :type request_json: str
+ """
+ if request_json == PING_REQUEST:
+ # do not process request if it's just a ping
+ return
+
+ try:
+ # request = zmq.utils.jsonapi.loads(request_json)
+ # We use stdlib's json to ensure that we get unicode strings
+ request = json.loads(request_json)
+ api_method = request['api_method']
+ kwargs = request['arguments'] or None
+ except Exception as e:
+ msg = "Malformed JSON data in Backend request '{0}'. Exc: {1!r}"
+ msg = msg.format(request_json, e)
+ msg = msg.format(request_json)
+ logger.critical(msg)
+ raise
+
+ if api_method not in API:
+ logger.error("Invalid API call '{0}'".format(api_method))
+ return
+
+ self._run_in_thread(api_method, kwargs)
+
+ def _run_in_thread(self, api_method, kwargs):
+ """
+ Run the method name in a thread with the given arguments.
+
+ :param api_method: the callable name to run in a thread.
+ :type api_method: str
+ :param kwargs: the arguments dict that will be sent to the callable.
+ :type kwargs: tuple
+ """
+ func = getattr(self, api_method)
+
+ method = func
+ if kwargs is not None:
+ method = lambda: func(**kwargs)
+
+ # logger.debug("Running method: '{0}' "
+ # "with args: '{1}' in a thread".format(api_method, kwargs))
+
+ # run the action in a thread and keep track of it
+ d = threads.deferToThread(method)
+ d.addCallback(self._done_action, d)
+ d.addErrback(self._done_action, d)
+ self._ongoing_defers.append(d)
+
+ def _done_action(self, failure, d):
+ """
+ Remove the defer from the ongoing list.
+
+ :param failure: the failure that triggered the errback.
+ None if no error.
+ :type failure: twisted.python.failure.Failure
+ :param d: defer to remove
+ :type d: twisted.internet.defer.Deferred
+ """
+ if failure is not None:
+ if failure.check(defer.CancelledError):
+ logger.debug("A defer was cancelled.")
+ else:
+ logger.error("There was a failure - {0!r}".format(failure))
+ logger.error(failure.getTraceback())
+
+ if d in self._ongoing_defers:
+ self._ongoing_defers.remove(d)
diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py
new file mode 100644
index 00000000..e2611251
--- /dev/null
+++ b/src/leap/bitmask/backend/backend_proxy.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+# backend_proxy.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+The BackendProxy handles calls from the GUI and forwards (through ZMQ)
+to the backend.
+"""
+# XXX should document the relationship to the API here.
+
+import functools
+import Queue
+import threading
+import time
+
+import zmq
+
+from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST
+from leap.bitmask.backend.utils import get_backend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class BackendProxy(object):
+ """
+ The BackendProxy handles calls from the GUI and forwards (through ZMQ)
+ to the backend.
+ """
+
+ PORT = '5556'
+ SERVER = "tcp://localhost:%s" % PORT
+
+ POLL_TIMEOUT = 4000 # ms
+ POLL_TRIES = 3
+
+ PING_INTERVAL = 2 # secs
+
+ def __init__(self):
+ self._socket = None
+
+ # initialize ZMQ stuff:
+ context = zmq.Context()
+ logger.debug("Connecting to server...")
+ socket = context.socket(zmq.REQ)
+
+ # public, secret = zmq.curve_keypair()
+ client_keys = zmq.curve_keypair()
+ socket.curve_publickey = client_keys[0]
+ socket.curve_secretkey = client_keys[1]
+
+ # The client must know the server's public key to make a CURVE
+ # connection.
+ public, _ = get_backend_certificates()
+ socket.curve_serverkey = public
+
+ socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.connect(self.SERVER)
+ self._socket = socket
+
+ self._ping_at = 0
+ self.online = False
+
+ self._call_queue = Queue.Queue()
+ self._worker_caller = threading.Thread(target=self._worker)
+ self._worker_caller.start()
+
+ def _worker(self):
+ """
+ Worker loop that processes the Queue of pending requests to do.
+ """
+ while True:
+ try:
+ request = self._call_queue.get(block=False)
+ # break the loop after sending the 'stop' action to the
+ # backend.
+ if request == STOP_REQUEST:
+ break
+
+ self._send_request(request)
+ except Queue.Empty:
+ pass
+ time.sleep(0.01)
+ self._ping()
+
+ logger.debug("BackendProxy worker stopped.")
+
+ def _reset_ping(self):
+ """
+ Reset the ping timeout counter.
+ This is called for every ping and request.
+ """
+ self._ping_at = time.time() + self.PING_INTERVAL
+
+ def _ping(self):
+ """
+ Heartbeat helper.
+ Sends a PING request just to know that the server is alive.
+ """
+ if time.time() > self._ping_at:
+ self._send_request(PING_REQUEST)
+ self._reset_ping()
+
+ def _api_call(self, *args, **kwargs):
+ """
+ Call the `api_method` method in backend (through zmq).
+
+ :param kwargs: named arguments to forward to the backend api method.
+ :type kwargs: dict
+
+ Note: is mandatory to have the kwarg 'api_method' defined.
+ """
+ if args:
+ # Use a custom message to be more clear about using kwargs *only*
+ raise Exception("All arguments need to be kwargs!")
+
+ api_method = kwargs.pop('api_method', None)
+ if api_method is None:
+ raise Exception("Missing argument, no method name specified.")
+
+ request = {
+ 'api_method': api_method,
+ 'arguments': kwargs,
+ }
+
+ try:
+ request_json = zmq.utils.jsonapi.dumps(request)
+ except Exception as e:
+ msg = ("Error serializing request into JSON.\n"
+ "Exception: {0} Data: {1}")
+ msg = msg.format(e, request)
+ logger.critical(msg)
+ raise
+
+ # queue the call in order to handle the request in a thread safe way.
+ self._call_queue.put(request_json)
+
+ if api_method == STOP_REQUEST:
+ self._call_queue.put(STOP_REQUEST)
+
+ def _send_request(self, request):
+ """
+ Send the given request to the server.
+ This is used from a thread safe loop in order to avoid sending a
+ request without receiving a response from a previous one.
+
+ :param request: the request to send.
+ :type request: str
+ """
+ # logger.debug("Sending request to backend: {0}".format(request))
+ self._socket.send(request)
+
+ poll = zmq.Poller()
+ poll.register(self._socket, zmq.POLLIN)
+
+ reply = None
+ tries = 0
+
+ while True:
+ socks = dict(poll.poll(self.POLL_TIMEOUT))
+ if socks.get(self._socket) == zmq.POLLIN:
+ reply = self._socket.recv()
+ break
+
+ tries += 1
+ if tries < self.POLL_TRIES:
+ logger.warning('Retrying receive... {0}/{1}'.format(
+ tries, self.POLL_TRIES))
+ else:
+ break
+
+ if reply is None:
+ msg = "Timeout error contacting backend."
+ logger.critical(msg)
+ self.online = False
+ else:
+ # msg = "Received reply for '{0}' -> '{1}'".format(request, reply)
+ # logger.debug(msg)
+ self.online = True
+ # request received, no ping needed for other interval.
+ self._reset_ping()
+
+ def __getattribute__(self, name):
+ """
+ This allows the user to do:
+ bp = BackendProxy()
+ bp.some_method()
+
+ Just by having defined 'some_method' in the API
+
+ :param name: the attribute name that is requested.
+ :type name: str
+ """
+ if name in API:
+ return functools.partial(self._api_call, api_method=name)
+ else:
+ return object.__getattribute__(self, name)
diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
index 19fcf283..f721086b 100644
--- a/src/leap/bitmask/backend/components.py
+++ b/src/leap/bitmask/backend/components.py
@@ -31,6 +31,7 @@ from twisted.python import log
import zope.interface
import zope.proxy
+from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.crypto.srpregister import SRPRegister
@@ -68,7 +69,6 @@ class ILEAPComponent(zope.interface.Interface):
"""
Interface that every component for the backend should comply to
"""
-
key = zope.interface.Attribute("Key id for this component")
@@ -197,7 +197,7 @@ class Provider(object):
else:
if self._signaler is not None:
self._signaler.signal(
- self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY)
+ self._signaler.prov_problem_with_provider)
logger.error("Could not load provider configuration.")
self._login_widget.set_enabled(True)
@@ -234,7 +234,7 @@ class Provider(object):
services = get_supported(self._get_services(domain))
self._signaler.signal(
- self._signaler.PROV_GET_SUPPORTED_SERVICES, services)
+ self._signaler.prov_get_supported_services, services)
def get_all_services(self, providers):
"""
@@ -253,7 +253,7 @@ class Provider(object):
services_all = services_all.union(set(services))
self._signaler.signal(
- self._signaler.PROV_GET_ALL_SERVICES, services_all)
+ self._signaler.prov_get_all_services, list(services_all))
def get_details(self, domain, lang=None):
"""
@@ -268,7 +268,7 @@ class Provider(object):
prov_get_details -> dict
"""
self._signaler.signal(
- self._signaler.PROV_GET_DETAILS,
+ self._signaler.prov_get_details,
self._provider_config.get_light_config(domain, lang))
def get_pinned_providers(self):
@@ -279,7 +279,7 @@ class Provider(object):
prov_get_pinned_providers -> list of provider domains
"""
self._signaler.signal(
- self._signaler.PROV_GET_PINNED_PROVIDERS,
+ self._signaler.prov_get_pinned_providers,
PinnedProviders.domains())
@@ -324,7 +324,7 @@ class Register(object):
partial(srpregister.register_user, username, password))
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED)
+ self._signaler.signal(self._signaler.srp_registration_failed)
logger.error("Could not load provider configuration.")
@@ -401,12 +401,12 @@ class EIP(object):
if not self._can_start(domain):
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED)
+ self._signaler.signal(self._signaler.eip_connection_aborted)
return
if not loaded:
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED)
+ self._signaler.signal(self._signaler.eip_connection_aborted)
logger.error("Tried to start EIP but cannot find any "
"available provider!")
return
@@ -425,28 +425,28 @@ class EIP(object):
if not self._provider_config.loaded():
# This means that the user didn't call setup_eip first.
- self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), "
+ self._signaler.signal(signaler.backend_bad_call, "EIP.start(), "
"no provider loaded")
return
try:
self._start_eip(*args, **kwargs)
except vpnprocess.OpenVPNAlreadyRunning:
- signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING)
+ signaler.signal(signaler.eip_openvpn_already_running)
except vpnprocess.AlienOpenVPNAlreadyRunning:
- signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING)
+ signaler.signal(signaler.eip_alien_openvpn_already_running)
except vpnlauncher.OpenVPNNotFoundException:
- signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR)
+ signaler.signal(signaler.eip_openvpn_not_found_error)
except vpnlauncher.VPNLauncherException:
# TODO: this seems to be used for 'gateway not found' only.
# see vpnlauncher.py
- signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION)
+ signaler.signal(signaler.eip_vpn_launcher_exception)
except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable:
- signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR)
+ signaler.signal(signaler.eip_no_polkit_agent_error)
except linuxvpnlauncher.EIPNoPkexecAvailable:
- signaler.signal(signaler.EIP_NO_PKEXEC_ERROR)
+ signaler.signal(signaler.eip_no_pkexec_error)
except darwinvpnlauncher.EIPNoTunKextLoaded:
- signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR)
+ signaler.signal(signaler.eip_no_tun_kext_error)
except Exception as e:
logger.error("Unexpected problem: {0!r}".format(e))
else:
@@ -482,12 +482,12 @@ class EIP(object):
while retry <= MAX_FW_WAIT_RETRIES:
if self._vpn.is_fw_down():
- self._signaler.signal(self._signaler.EIP_STOPPED)
+ self._signaler.signal(self._signaler.eip_stopped)
return
else:
- #msg = "Firewall is not down yet, waiting... {0} of {1}"
- #msg = msg.format(retry, MAX_FW_WAIT_RETRIES)
- #logger.debug(msg)
+ # msg = "Firewall is not down yet, waiting... {0} of {1}"
+ # msg = msg.format(retry, MAX_FW_WAIT_RETRIES)
+ # logger.debug(msg)
time.sleep(FW_WAIT_STEP)
retry += 1
logger.warning("After waiting, firewall is not down... "
@@ -542,7 +542,7 @@ class EIP(object):
filtered_domains.append((domain, is_initialized))
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS,
+ self._signaler.signal(self._signaler.eip_get_initialized_providers,
filtered_domains)
def tear_fw_down(self):
@@ -551,6 +551,12 @@ class EIP(object):
"""
self._vpn.tear_down_firewall()
+ def bitmask_root_vpn_down(self):
+ """
+ Bring openvpn down, using bitmask-root helper.
+ """
+ self._vpn.bitmask_root_vpn_down()
+
def get_gateways_list(self, domain):
"""
Signal a list of gateways for the given provider.
@@ -566,7 +572,7 @@ class EIP(object):
if not self._provider_is_initialized(domain):
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_UNINITIALIZED_PROVIDER)
+ self._signaler.eip_uninitialized_provider)
return
eip_config = eipconfig.EIPConfig()
@@ -580,14 +586,55 @@ class EIP(object):
if not eip_loaded or provider_config is None:
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_GET_GATEWAYS_LIST_ERROR)
+ self._signaler.eip_get_gateways_list_error)
return
gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list()
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_GET_GATEWAYS_LIST, gateways)
+ self._signaler.eip_get_gateways_list, gateways)
+
+ def get_gateway_country_code(self, domain):
+ """
+ Signal the country code for the currently used gateway for the given
+ provider.
+
+ :param domain: the domain to get country code.
+ :type domain: str
+
+ Signals:
+ eip_get_gateway_country_code -> str
+ eip_no_gateway
+ """
+ settings = Settings()
+
+ eip_config = eipconfig.EIPConfig()
+ provider_config = ProviderConfig.get_provider_config(domain)
+
+ api_version = provider_config.get_api_version()
+ eip_config.set_api_version(api_version)
+ eip_config.load(eipconfig.get_eipconfig_path(domain))
+
+ gateway_selector = eipconfig.VPNGatewaySelector(eip_config)
+ gateway_conf = settings.get_selected_gateway(domain)
+
+ if gateway_conf == GATEWAY_AUTOMATIC:
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
+
+ if not gateways:
+ self._signaler.signal(self._signaler.eip_no_gateway)
+ return
+
+ # this only works for selecting the first gateway, as we're
+ # currently doing.
+ ccodes = gateway_selector.get_gateways_country_code()
+ gateway_ccode = ccodes[gateways[0]]
+
+ self._signaler.signal(self._signaler.eip_get_gateway_country_code,
+ gateway_ccode)
def _can_start(self, domain):
"""
@@ -607,7 +654,8 @@ class EIP(object):
launcher = get_vpn_launcher()
ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH)
if not os.path.isfile(ovpn_path):
- logger.error("Cannot start OpenVPN, binary not found")
+ logger.error("Cannot start OpenVPN, binary not found: %s" %
+ (ovpn_path,))
return False
# check for other problems
@@ -643,10 +691,10 @@ class EIP(object):
"""
if self._can_start(domain):
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CAN_START)
+ self._signaler.signal(self._signaler.eip_can_start)
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CANNOT_START)
+ self._signaler.signal(self._signaler.eip_cannot_start)
def check_dns(self, domain):
"""
@@ -665,7 +713,7 @@ class EIP(object):
"""
Callback handler for `do_check`.
"""
- self._signaler.signal(self._signaler.EIP_DNS_OK)
+ self._signaler.signal(self._signaler.eip_dns_ok)
logger.debug("DNS check OK")
def check_err(failure):
@@ -677,7 +725,7 @@ class EIP(object):
"""
logger.debug("Can't resolve hostname. {0!r}".format(failure))
- self._signaler.signal(self._signaler.EIP_DNS_ERROR)
+ self._signaler.signal(self._signaler.eip_dns_error)
# python 2.7.4 raises socket.error
# python 2.7.5 raises socket.gaierror
@@ -737,7 +785,7 @@ class Soledad(object):
self._soledad_defer.addCallback(self._set_proxies_cb)
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED)
+ self._signaler.signal(self._signaler.soledad_bootstrap_failed)
logger.error("Could not load provider configuration.")
return self._soledad_defer
@@ -793,7 +841,7 @@ class Soledad(object):
Password change callback.
"""
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK)
+ self._signaler.signal(self._signaler.soledad_password_change_ok)
def _change_password_error(self, failure):
"""
@@ -808,7 +856,7 @@ class Soledad(object):
logger.error("Passphrase too short.")
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR)
+ self._signaler.signal(self._signaler.soledad_password_change_error)
def change_password(self, new_password):
"""
@@ -866,7 +914,7 @@ class Keymanager(object):
new_key = keys_file.read()
except IOError as e:
logger.error("IOError importing key. {0!r}".format(e))
- signal = self._signaler.KEYMANAGER_IMPORT_IOERROR
+ signal = self._signaler.keymanager_import_ioerror
self._signaler.signal(signal)
return
@@ -876,19 +924,19 @@ class Keymanager(object):
new_key)
except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
logger.error(repr(e))
- signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH
+ signal = self._signaler.keymanager_import_datamismatch
self._signaler.signal(signal)
return
if public_key is None or private_key is None:
- signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY
+ signal = self._signaler.keymanager_import_missingkey
self._signaler.signal(signal)
return
current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey)
if public_key.address != current_public_key.address:
logger.error("The key does not match the ID")
- signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH
+ signal = self._signaler.keymanager_import_addressmismatch
self._signaler.signal(signal)
return
@@ -899,7 +947,7 @@ class Keymanager(object):
keymanager.send_key(openpgp.OpenPGPKey)
logger.debug('Import ok')
- signal = self._signaler.KEYMANAGER_IMPORT_OK
+ signal = self._signaler.keymanager_import_ok
self._signaler.signal(signal)
@@ -923,17 +971,17 @@ class Keymanager(object):
keys_file.write(private_key.key_data)
logger.debug('Export ok')
- self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK)
+ self._signaler.signal(self._signaler.keymanager_export_ok)
except IOError as e:
logger.error("IOError exporting key. {0!r}".format(e))
- self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR)
+ self._signaler.signal(self._signaler.keymanager_export_error)
def list_keys(self):
"""
List all the keys stored in the local DB.
"""
keys = self._keymanager_proxy.get_all_keys_in_local_db()
- self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys)
+ self._signaler.signal(self._signaler.keymanager_keys_list, keys)
def get_key_details(self, username):
"""
@@ -942,7 +990,7 @@ class Keymanager(object):
public_key = self._keymanager_proxy.get_key(username,
openpgp.OpenPGPKey)
details = (public_key.key_id, public_key.fingerprint)
- self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details)
+ self._signaler.signal(self._signaler.keymanager_key_details, details)
class Mail(object):
@@ -1027,7 +1075,7 @@ class Mail(object):
logger.debug('Waiting for imap service to stop.')
cv.wait(self.SERVICE_STOP_TIMEOUT)
logger.debug('IMAP stopped')
- self._signaler.signal(self._signaler.IMAP_STOPPED)
+ self._signaler.signal(self._signaler.imap_stopped)
def stop_imap_service(self):
"""
@@ -1080,7 +1128,7 @@ class Authenticate(object):
return self._login_defer
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_AUTH_ERROR)
+ self._signaler.signal(self._signaler.srp_auth_error)
logger.error("Could not load provider configuration.")
def cancel_login(self):
@@ -1105,7 +1153,7 @@ class Authenticate(object):
"""
if not self._is_logged_in():
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR)
+ self._signaler.signal(self._signaler.srp_not_logged_in_error)
return
return self._srp_auth.change_password(current_password, new_password)
@@ -1117,7 +1165,7 @@ class Authenticate(object):
"""
if not self._is_logged_in():
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR)
+ self._signaler.signal(self._signaler.srp_not_logged_in_error)
return
self._srp_auth.logout()
@@ -1140,8 +1188,8 @@ class Authenticate(object):
signal = None
if self._is_logged_in():
- signal = self._signaler.SRP_STATUS_LOGGED_IN
+ signal = self._signaler.srp_status_logged_in
else:
- signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN
+ signal = self._signaler.srp_status_not_logged_in
self._signaler.signal(signal)
diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py
index 3c5222f4..3b023563 100644
--- a/src/leap/bitmask/backend/leapbackend.py
+++ b/src/leap/bitmask/backend/leapbackend.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# leapbackend.py
-# Copyright (C) 2013 LEAP
+# Copyright (C) 2013, 2014 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
@@ -15,178 +15,65 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-Backend for GUI/Logic communication.
+Backend for everything
"""
import logging
-from Queue import Queue, Empty
-
-from twisted.internet import reactor
-from twisted.internet import threads, defer
-from twisted.internet.task import LoopingCall
-
import zope.interface
import zope.proxy
-from leap.bitmask.backend.leapsignaler import Signaler
from leap.bitmask.backend import components
+from leap.bitmask.backend.backend import Backend
+from leap.bitmask.backend.settings import Settings
logger = logging.getLogger(__name__)
+ERROR_KEY = "error"
+PASSED_KEY = "passed"
-class Backend(object):
+
+class LeapBackend(Backend):
"""
- Backend for everything, the UI should only use this class.
+ Backend server subclass, used to implement the API methods.
"""
-
- PASSED_KEY = "passed"
- ERROR_KEY = "error"
-
- def __init__(self, bypass_checks=False):
+ def __init__(self, bypass_checks=False, frontend_pid=None):
"""
Constructor for the backend.
"""
- # Components map for the commands received
- self._components = {}
-
- # Ongoing defers that will be cancelled at stop time
- self._ongoing_defers = []
+ Backend.__init__(self, frontend_pid)
- # Signaler object to translate commands into Qt signals
- self._signaler = Signaler()
+ self._settings = Settings()
# Objects needed by several components, so we make a proxy and pass
# them around
self._soledad_proxy = zope.proxy.ProxyBase(None)
self._keymanager_proxy = zope.proxy.ProxyBase(None)
- # Component registration
- self._register(components.Provider(self._signaler, bypass_checks))
- self._register(components.Register(self._signaler))
- self._register(components.Authenticate(self._signaler))
- self._register(components.EIP(self._signaler))
- self._register(components.Soledad(self._soledad_proxy,
- self._keymanager_proxy,
- self._signaler))
- self._register(components.Keymanager(self._keymanager_proxy,
- self._signaler))
- self._register(components.Mail(self._soledad_proxy,
- self._keymanager_proxy,
- self._signaler))
-
- # We have a looping call on a thread executing all the
- # commands in queue. Right now this queue is an actual Queue
- # object, but it'll become the zmq recv_multipart queue
- self._lc = LoopingCall(threads.deferToThread, self._worker)
-
- # Temporal call_queue for worker, will be replaced with
- # recv_multipart os something equivalent in the loopingcall
- self._call_queue = Queue()
-
- @property
- def signaler(self):
- """
- Public signaler access to let the UI connect to its signals.
- """
- return self._signaler
-
- def start(self):
- """
- Starts the looping call
- """
- logger.debug("Starting worker...")
- self._lc.start(0.01)
-
- def stop(self):
- """
- Stops the looping call and tries to cancel all the defers.
- """
- reactor.callLater(2, self._stop)
+ # Component instances creation
+ self._provider = components.Provider(self._signaler, bypass_checks)
+ self._register = components.Register(self._signaler)
+ self._authenticate = components.Authenticate(self._signaler)
+ self._eip = components.EIP(self._signaler)
+ self._soledad = components.Soledad(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler)
+ self._keymanager = components.Keymanager(self._keymanager_proxy,
+ self._signaler)
+ self._mail = components.Mail(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler)
- def _stop(self):
+ def _check_type(self, obj, expected_type):
"""
- Delayed stopping of worker. Called from `stop`.
- """
- logger.debug("Stopping worker...")
- if self._lc.running:
- self._lc.stop()
- else:
- logger.warning("Looping call is not running, cannot stop")
-
- logger.debug("Cancelling ongoing defers...")
- while len(self._ongoing_defers) > 0:
- d = self._ongoing_defers.pop()
- d.cancel()
- logger.debug("Defers cancelled.")
+ Check the type of a parameter.
- def _register(self, component):
+ :param obj: object to check its type.
+ :type obj: any type
+ :param expected_type: the expected type of the object.
+ :type expected_type: type
"""
- Registers a component in this backend
-
- :param component: Component to register
- :type component: any object that implements ILEAPComponent
- """
- # TODO: assert that the component implements the interfaces
- # expected
- try:
- self._components[component.key] = component
- except Exception:
- logger.error("There was a problem registering %s" % (component,))
-
- def _signal_back(self, _, signal):
- """
- Helper method to signal back (callback like behavior) to the
- UI that an operation finished.
-
- :param signal: signal name
- :type signal: str
- """
- self._signaler.signal(signal)
-
- def _worker(self):
- """
- Worker method, called from a different thread and as a part of
- a looping call
- """
- try:
- # this'll become recv_multipart
- cmd = self._call_queue.get(block=False)
-
- # cmd is: component, method, signalback, *args
- func = getattr(self._components[cmd[0]], cmd[1])
- d = func(*cmd[3:])
- if d is not None: # d may be None if a defer chain is cancelled.
- # A call might not have a callback signal, but if it does,
- # we add it to the chain
- if cmd[2] is not None:
- d.addCallbacks(self._signal_back, logger.error, cmd[2])
- d.addCallbacks(self._done_action, logger.error,
- callbackKeywords={"d": d})
- d.addErrback(logger.error)
- self._ongoing_defers.append(d)
- except Empty:
- # If it's just empty we don't have anything to do.
- pass
- except defer.CancelledError:
- logger.debug("defer cancelled somewhere (CancelledError).")
- except Exception as e:
- # But we log the rest
- logger.exception("Unexpected exception: {0!r}".format(e))
-
- def _done_action(self, _, d):
- """
- Remover of the defer once it's done
-
- :param d: defer to remove
- :type d: twisted.internet.defer.Deferred
- """
- if d in self._ongoing_defers:
- self._ongoing_defers.remove(d)
-
- # XXX: Temporal interface until we migrate to zmq
- # We simulate the calls to zmq.send_multipart. Once we separate
- # this in two processes, the methods bellow can be changed to
- # send_multipart and this backend class will be really simple.
+ if not isinstance(obj, expected_type):
+ raise TypeError("The parameter type is incorrect.")
def provider_setup(self, provider):
"""
@@ -202,13 +89,13 @@ class Backend(object):
prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str }
prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str }
"""
- self._call_queue.put(("provider", "setup_provider", None, provider))
+ self._provider.setup_provider(provider)
def provider_cancel_setup(self):
"""
Cancel the ongoing setup provider (if any).
"""
- self._call_queue.put(("provider", "cancel_setup_provider", None))
+ self._provider.cancel_setup_provider()
def provider_bootstrap(self, provider):
"""
@@ -223,7 +110,7 @@ class Backend(object):
prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str}
prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str}
"""
- self._call_queue.put(("provider", "bootstrap", None, provider))
+ self._provider.bootstrap(provider)
def provider_get_supported_services(self, domain):
"""
@@ -235,8 +122,7 @@ class Backend(object):
Signals:
prov_get_supported_services -> list of unicode
"""
- self._call_queue.put(("provider", "get_supported_services", None,
- domain))
+ self._provider.get_supported_services(domain)
def provider_get_all_services(self, providers):
"""
@@ -248,13 +134,11 @@ class Backend(object):
Signals:
prov_get_all_services -> list of unicode
"""
- self._call_queue.put(("provider", "get_all_services", None,
- providers))
+ self._provider.get_all_services(providers)
def provider_get_details(self, domain, lang):
"""
- Signal a ProviderConfigLight object with the current ProviderConfig
- settings.
+ Signal a dict with the current ProviderConfig settings.
:param domain: the domain name of the provider.
:type domain: str
@@ -262,9 +146,9 @@ class Backend(object):
:type lang: str
Signals:
- prov_get_details -> ProviderConfigLight
+ prov_get_details -> dict
"""
- self._call_queue.put(("provider", "get_details", None, domain, lang))
+ self._provider.get_details(domain, lang)
def provider_get_pinned_providers(self):
"""
@@ -273,7 +157,7 @@ class Backend(object):
Signals:
prov_get_pinned_providers -> list of provider domains
"""
- self._call_queue.put(("provider", "get_pinned_providers", None))
+ self._provider.get_pinned_providers()
def user_register(self, provider, username, password):
"""
@@ -291,8 +175,7 @@ class Backend(object):
srp_registration_taken
srp_registration_failed
"""
- self._call_queue.put(("register", "register_user", None, provider,
- username, password))
+ self._register.register_user(provider, username, password)
def eip_setup(self, provider, skip_network=False):
"""
@@ -309,14 +192,13 @@ class Backend(object):
eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
eip_cancelled_setup
"""
- self._call_queue.put(("eip", "setup_eip", None, provider,
- skip_network))
+ self._eip.setup_eip(provider, skip_network)
def eip_cancel_setup(self):
"""
Cancel the ongoing setup EIP (if any).
"""
- self._call_queue.put(("eip", "cancel_setup_eip", None))
+ self._eip.cancel_setup_eip()
def eip_start(self, restart=False):
"""
@@ -343,7 +225,7 @@ class Backend(object):
:param restart: whether is is a restart.
:type restart: bool
"""
- self._call_queue.put(("eip", "start", None, restart))
+ self._eip.start(restart)
def eip_stop(self, shutdown=False, restart=False, failed=False):
"""
@@ -355,13 +237,13 @@ class Backend(object):
:param restart: whether this is part of a restart.
:type restart: bool
"""
- self._call_queue.put(("eip", "stop", None, shutdown, restart))
+ self._eip.stop(shutdown, restart)
def eip_terminate(self):
"""
Terminate the EIP service, not necessarily in a nice way.
"""
- self._call_queue.put(("eip", "terminate", None))
+ self._eip.terminate()
def eip_get_gateways_list(self, domain):
"""
@@ -370,16 +252,25 @@ class Backend(object):
:param domain: the domain to get the gateways.
:type domain: str
- # TODO discuss how to document the expected result object received of
- # the signal
- :signal type: list of str
-
Signals:
eip_get_gateways_list -> list of unicode
eip_get_gateways_list_error
eip_uninitialized_provider
"""
- self._call_queue.put(("eip", "get_gateways_list", None, domain))
+ self._eip.get_gateways_list(domain)
+
+ def eip_get_gateway_country_code(self, domain):
+ """
+ Signal a list of gateways for the given provider.
+
+ :param domain: the domain to get the gateways.
+ :type domain: str
+
+ Signals:
+ eip_get_gateways_list -> str
+ eip_no_gateway
+ """
+ self._eip.get_gateway_country_code(domain)
def eip_get_initialized_providers(self, domains):
"""
@@ -392,8 +283,7 @@ class Backend(object):
eip_get_initialized_providers -> list of tuple(unicode, bool)
"""
- self._call_queue.put(("eip", "get_initialized_providers",
- None, domains))
+ self._eip.get_initialized_providers(domains)
def eip_can_start(self, domain):
"""
@@ -406,8 +296,7 @@ class Backend(object):
eip_can_start
eip_cannot_start
"""
- self._call_queue.put(("eip", "can_start",
- None, domain))
+ self._eip.can_start(domain)
def eip_check_dns(self, domain):
"""
@@ -420,13 +309,19 @@ class Backend(object):
eip_dns_ok
eip_dns_error
"""
- self._call_queue.put(("eip", "check_dns", None, domain))
+ self._eip.check_dns(domain)
def tear_fw_down(self):
"""
Signal the need to tear the fw down.
"""
- self._call_queue.put(("eip", "tear_fw_down", None))
+ self._eip.tear_fw_down()
+
+ def bitmask_root_vpn_down(self):
+ """
+ Signal the need to bring vpn down.
+ """
+ self._eip.bitmask_root_vpn_down()
def user_login(self, provider, username, password):
"""
@@ -447,8 +342,7 @@ class Backend(object):
srp_auth_connection_error
srp_auth_error
"""
- self._call_queue.put(("authenticate", "login", None, provider,
- username, password))
+ self._authenticate.login(provider, username, password)
def user_logout(self):
"""
@@ -459,13 +353,13 @@ class Backend(object):
srp_logout_error
srp_not_logged_in_error
"""
- self._call_queue.put(("authenticate", "logout", None))
+ self._authenticate.logout()
def user_cancel_login(self):
"""
Cancel the ongoing login (if any).
"""
- self._call_queue.put(("authenticate", "cancel_login", None))
+ self._authenticate.cancel_login()
def user_change_password(self, current_password, new_password):
"""
@@ -482,8 +376,7 @@ class Backend(object):
srp_password_change_badpw
srp_password_change_error
"""
- self._call_queue.put(("authenticate", "change_password", None,
- current_password, new_password))
+ self._authenticate.change_password(current_password, new_password)
def soledad_change_password(self, new_password):
"""
@@ -498,8 +391,7 @@ class Backend(object):
srp_password_change_badpw
srp_password_change_error
"""
- self._call_queue.put(("soledad", "change_password", None,
- new_password))
+ self._soledad.change_password(new_password)
def user_get_logged_in_status(self):
"""
@@ -509,7 +401,7 @@ class Backend(object):
srp_status_logged_in
srp_status_not_logged_in
"""
- self._call_queue.put(("authenticate", "get_logged_in_status", None))
+ self._authenticate.get_logged_in_status()
def soledad_bootstrap(self, username, domain, password):
"""
@@ -527,8 +419,10 @@ class Backend(object):
soledad_bootstrap_failed
soledad_invalid_auth_token
"""
- self._call_queue.put(("soledad", "bootstrap", None,
- username, domain, password))
+ self._check_type(username, unicode)
+ self._check_type(domain, unicode)
+ self._check_type(password, unicode)
+ self._soledad.bootstrap(username, domain, password)
def soledad_load_offline(self, username, password, uuid):
"""
@@ -543,20 +437,19 @@ class Backend(object):
Signals:
"""
- self._call_queue.put(("soledad", "load_offline", None,
- username, password, uuid))
+ self._soledad.load_offline(username, password, uuid)
def soledad_cancel_bootstrap(self):
"""
Cancel the ongoing soledad bootstrapping process (if any).
"""
- self._call_queue.put(("soledad", "cancel_bootstrap", None))
+ self._soledad.cancel_bootstrap()
def soledad_close(self):
"""
Close soledad database.
"""
- self._call_queue.put(("soledad", "close", None))
+ self._soledad.close()
def keymanager_list_keys(self):
"""
@@ -565,7 +458,7 @@ class Backend(object):
Signals:
keymanager_keys_list -> list
"""
- self._call_queue.put(("keymanager", "list_keys", None))
+ self._keymanager.list_keys()
def keymanager_export_keys(self, username, filename):
"""
@@ -580,8 +473,7 @@ class Backend(object):
keymanager_export_ok
keymanager_export_error
"""
- self._call_queue.put(("keymanager", "export_keys", None,
- username, filename))
+ self._keymanager.export_keys(username, filename)
def keymanager_get_key_details(self, username):
"""
@@ -593,7 +485,7 @@ class Backend(object):
Signals:
keymanager_key_details
"""
- self._call_queue.put(("keymanager", "get_key_details", None, username))
+ self._keymanager.get_key_details(username)
def smtp_start_service(self, full_user_id, download_if_needed=False):
"""
@@ -605,8 +497,7 @@ class Backend(object):
for the file
:type download_if_needed: bool
"""
- self._call_queue.put(("mail", "start_smtp_service", None,
- full_user_id, download_if_needed))
+ self._mail.start_smtp_service(full_user_id, download_if_needed)
def imap_start_service(self, full_user_id, offline=False):
"""
@@ -617,14 +508,13 @@ class Backend(object):
:param offline: whether imap should start in offline mode or not.
:type offline: bool
"""
- self._call_queue.put(("mail", "start_imap_service", None,
- full_user_id, offline))
+ self._mail.start_imap_service(full_user_id, offline)
def smtp_stop_service(self):
"""
Stop the SMTP service.
"""
- self._call_queue.put(("mail", "stop_smtp_service", None))
+ self._mail.stop_smtp_service()
def imap_stop_service(self):
"""
@@ -633,4 +523,15 @@ class Backend(object):
Signals:
imap_stopped
"""
- self._call_queue.put(("mail", "stop_imap_service", None))
+ self._mail.stop_imap_service()
+
+ def settings_set_selected_gateway(self, provider, gateway):
+ """
+ Set the selected gateway for a given provider.
+
+ :param provider: provider domain
+ :type provider: str
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+ self._settings.set_selected_gateway(provider, gateway)
diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py
index da8908fd..c0fdffdc 100644
--- a/src/leap/bitmask/backend/leapsignaler.py
+++ b/src/leap/bitmask/backend/leapsignaler.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# components.py
+# leapsignaler.py
# Copyright (C) 2013 LEAP
#
# This program is free software: you can redistribute it and/or modify
@@ -15,371 +15,102 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-Signaler for Backend/Frontend communication.
+Signaling server, used to define the API signals.
"""
-import logging
-
from PySide import QtCore
-logger = logging.getLogger(__name__)
+from leap.bitmask.backend.signaler_qt import SignalerQt
-class Signaler(QtCore.QObject):
+class LeapSignaler(SignalerQt):
"""
- Signaler object, handles converting string commands to Qt signals.
-
- This is intended for the separation in frontend/backend, this will
- live in the frontend.
+ Signaling server subclass, used to define the API signals.
"""
+ backend_bad_call = QtCore.Signal(object)
- ####################
- # These will only exist in the frontend
- # Signals for the ProviderBootstrapper
- prov_name_resolution = QtCore.Signal(object)
- prov_https_connection = QtCore.Signal(object)
- prov_download_provider_info = QtCore.Signal(object)
-
- prov_download_ca_cert = QtCore.Signal(object)
- prov_check_ca_fingerprint = QtCore.Signal(object)
- prov_check_api_certificate = QtCore.Signal(object)
-
- prov_problem_with_provider = QtCore.Signal(object)
-
- prov_unsupported_client = QtCore.Signal(object)
- prov_unsupported_api = QtCore.Signal(object)
-
- prov_get_all_services = QtCore.Signal(object)
- prov_get_supported_services = QtCore.Signal(object)
- prov_get_details = QtCore.Signal(object)
- prov_get_pinned_providers = QtCore.Signal(object)
-
- prov_cancelled_setup = QtCore.Signal(object)
-
- # Signals for SRPRegister
- srp_registration_finished = QtCore.Signal(object)
- srp_registration_failed = QtCore.Signal(object)
- srp_registration_taken = QtCore.Signal(object)
-
- # Signals for EIP bootstrapping
- eip_config_ready = QtCore.Signal(object)
+ eip_alien_openvpn_already_running = QtCore.Signal()
+ eip_can_start = QtCore.Signal()
+ eip_cancelled_setup = QtCore.Signal()
+ eip_cannot_start = QtCore.Signal()
eip_client_certificate_ready = QtCore.Signal(object)
-
- eip_cancelled_setup = QtCore.Signal(object)
-
- # Signals for SRPAuth
- srp_auth_ok = QtCore.Signal(object)
- srp_auth_error = QtCore.Signal(object)
- srp_auth_server_error = QtCore.Signal(object)
- srp_auth_connection_error = QtCore.Signal(object)
- srp_auth_bad_user_or_password = QtCore.Signal(object)
- srp_logout_ok = QtCore.Signal(object)
- srp_logout_error = QtCore.Signal(object)
- srp_password_change_ok = QtCore.Signal(object)
- srp_password_change_error = QtCore.Signal(object)
- srp_password_change_badpw = QtCore.Signal(object)
- srp_not_logged_in_error = QtCore.Signal(object)
- srp_status_logged_in = QtCore.Signal(object)
- srp_status_not_logged_in = QtCore.Signal(object)
-
- # Signals for EIP
- eip_connected = QtCore.Signal(object)
- eip_disconnected = QtCore.Signal(object)
+ eip_config_ready = QtCore.Signal(object)
+ eip_connected = QtCore.Signal()
+ eip_connection_aborted = QtCore.Signal()
eip_connection_died = QtCore.Signal(object)
- eip_connection_aborted = QtCore.Signal(object)
- eip_stopped = QtCore.Signal(object)
-
- eip_dns_ok = QtCore.Signal(object)
- eip_dns_error = QtCore.Signal(object)
-
- # EIP problems
- eip_no_polkit_agent_error = QtCore.Signal(object)
- eip_no_tun_kext_error = QtCore.Signal(object)
- eip_no_pkexec_error = QtCore.Signal(object)
- eip_openvpn_not_found_error = QtCore.Signal(object)
- eip_openvpn_already_running = QtCore.Signal(object)
- eip_alien_openvpn_already_running = QtCore.Signal(object)
- eip_vpn_launcher_exception = QtCore.Signal(object)
-
+ eip_disconnected = QtCore.Signal(object)
+ eip_dns_error = QtCore.Signal()
+ eip_dns_ok = QtCore.Signal()
+ eip_get_gateway_country_code = QtCore.Signal(object)
eip_get_gateways_list = QtCore.Signal(object)
- eip_get_gateways_list_error = QtCore.Signal(object)
- eip_uninitialized_provider = QtCore.Signal(object)
+ eip_get_gateways_list_error = QtCore.Signal()
eip_get_initialized_providers = QtCore.Signal(object)
-
- # signals from parsing openvpn output
- eip_network_unreachable = QtCore.Signal(object)
- eip_process_restart_tls = QtCore.Signal(object)
- eip_process_restart_ping = QtCore.Signal(object)
-
- # signals from vpnprocess.py
+ eip_network_unreachable = QtCore.Signal()
+ eip_no_gateway = QtCore.Signal()
+ eip_no_pkexec_error = QtCore.Signal()
+ eip_no_polkit_agent_error = QtCore.Signal()
+ eip_no_tun_kext_error = QtCore.Signal()
+ eip_openvpn_already_running = QtCore.Signal()
+ eip_openvpn_not_found_error = QtCore.Signal()
+ eip_process_finished = QtCore.Signal(int)
+ eip_process_restart_ping = QtCore.Signal()
+ eip_process_restart_tls = QtCore.Signal()
eip_state_changed = QtCore.Signal(dict)
eip_status_changed = QtCore.Signal(dict)
- eip_process_finished = QtCore.Signal(int)
+ eip_stopped = QtCore.Signal()
eip_tear_fw_down = QtCore.Signal(object)
-
- # signals whether the needed files to start EIP exist or not
- eip_can_start = QtCore.Signal(object)
- eip_cannot_start = QtCore.Signal(object)
-
- # Signals for Soledad
- soledad_bootstrap_failed = QtCore.Signal(object)
- soledad_bootstrap_finished = QtCore.Signal(object)
- soledad_offline_failed = QtCore.Signal(object)
- soledad_offline_finished = QtCore.Signal(object)
- soledad_invalid_auth_token = QtCore.Signal(object)
- soledad_cancelled_bootstrap = QtCore.Signal(object)
- soledad_password_change_ok = QtCore.Signal(object)
- soledad_password_change_error = QtCore.Signal(object)
-
- # Keymanager signals
- keymanager_export_ok = QtCore.Signal(object)
- keymanager_export_error = QtCore.Signal(object)
- keymanager_keys_list = QtCore.Signal(object)
-
- keymanager_import_ioerror = QtCore.Signal(object)
- keymanager_import_datamismatch = QtCore.Signal(object)
- keymanager_import_missingkey = QtCore.Signal(object)
- keymanager_import_addressmismatch = QtCore.Signal(object)
- keymanager_import_ok = QtCore.Signal(object)
-
+ eip_bitmask_root_vpn_down = QtCore.Signal(object)
+ eip_uninitialized_provider = QtCore.Signal()
+ eip_vpn_launcher_exception = QtCore.Signal()
+
+ imap_stopped = QtCore.Signal()
+
+ keymanager_export_error = QtCore.Signal()
+ keymanager_export_ok = QtCore.Signal()
+ keymanager_import_addressmismatch = QtCore.Signal()
+ keymanager_import_datamismatch = QtCore.Signal()
+ keymanager_import_ioerror = QtCore.Signal()
+ keymanager_import_missingkey = QtCore.Signal()
+ keymanager_import_ok = QtCore.Signal()
keymanager_key_details = QtCore.Signal(object)
+ keymanager_keys_list = QtCore.Signal(object)
- # mail related signals
- imap_stopped = QtCore.Signal(object)
-
- # This signal is used to warn the backend user that is doing something
- # wrong
- backend_bad_call = QtCore.Signal(object)
-
- ####################
- # These will exist both in the backend AND the front end.
- # The frontend might choose to not "interpret" all the signals
- # from the backend, but the backend needs to have all the signals
- # it's going to emit defined here
- PROV_NAME_RESOLUTION_KEY = "prov_name_resolution"
- PROV_HTTPS_CONNECTION_KEY = "prov_https_connection"
- PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info"
- PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert"
- PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint"
- PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate"
- PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider"
- PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client"
- PROV_UNSUPPORTED_API = "prov_unsupported_api"
- PROV_CANCELLED_SETUP = "prov_cancelled_setup"
- PROV_GET_ALL_SERVICES = "prov_get_all_services"
- PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services"
- PROV_GET_DETAILS = "prov_get_details"
- PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers"
-
- SRP_REGISTRATION_FINISHED = "srp_registration_finished"
- SRP_REGISTRATION_FAILED = "srp_registration_failed"
- SRP_REGISTRATION_TAKEN = "srp_registration_taken"
- SRP_AUTH_OK = "srp_auth_ok"
- SRP_AUTH_ERROR = "srp_auth_error"
- SRP_AUTH_SERVER_ERROR = "srp_auth_server_error"
- SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error"
- SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password"
- SRP_LOGOUT_OK = "srp_logout_ok"
- SRP_LOGOUT_ERROR = "srp_logout_error"
- SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok"
- SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error"
- SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw"
- SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error"
- SRP_STATUS_LOGGED_IN = "srp_status_logged_in"
- SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in"
-
- EIP_CONFIG_READY = "eip_config_ready"
- EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready"
- EIP_CANCELLED_SETUP = "eip_cancelled_setup"
-
- EIP_CONNECTED = "eip_connected"
- EIP_DISCONNECTED = "eip_disconnected"
- EIP_CONNECTION_DIED = "eip_connection_died"
- EIP_CONNECTION_ABORTED = "eip_connection_aborted"
- EIP_STOPPED = "eip_stopped"
-
- EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error"
- EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error"
- EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error"
- EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error"
- EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running"
- EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running"
- EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception"
-
- EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list"
- EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error"
- EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider"
- EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers"
-
- EIP_NETWORK_UNREACHABLE = "eip_network_unreachable"
- EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls"
- EIP_PROCESS_RESTART_PING = "eip_process_restart_ping"
-
- EIP_STATE_CHANGED = "eip_state_changed"
- EIP_STATUS_CHANGED = "eip_status_changed"
- EIP_PROCESS_FINISHED = "eip_process_finished"
- EIP_TEAR_FW_DOWN = "eip_tear_fw_down"
-
- EIP_CAN_START = "eip_can_start"
- EIP_CANNOT_START = "eip_cannot_start"
-
- EIP_DNS_OK = "eip_dns_ok"
- EIP_DNS_ERROR = "eip_dns_error"
-
- SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed"
- SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished"
- SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed"
- SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished"
- SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token"
-
- SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok"
- SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error"
-
- SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap"
-
- KEYMANAGER_EXPORT_OK = "keymanager_export_ok"
- KEYMANAGER_EXPORT_ERROR = "keymanager_export_error"
- KEYMANAGER_KEYS_LIST = "keymanager_keys_list"
-
- KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror"
- KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch"
- KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey"
- KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch"
- KEYMANAGER_IMPORT_OK = "keymanager_import_ok"
- KEYMANAGER_KEY_DETAILS = "keymanager_key_details"
-
- IMAP_STOPPED = "imap_stopped"
-
- BACKEND_BAD_CALL = "backend_bad_call"
-
- def __init__(self):
- """
- Constructor for the Signaler
- """
- QtCore.QObject.__init__(self)
- self._signals = {}
-
- signals = [
- self.PROV_NAME_RESOLUTION_KEY,
- self.PROV_HTTPS_CONNECTION_KEY,
- self.PROV_DOWNLOAD_PROVIDER_INFO_KEY,
- self.PROV_DOWNLOAD_CA_CERT_KEY,
- self.PROV_CHECK_CA_FINGERPRINT_KEY,
- self.PROV_CHECK_API_CERTIFICATE_KEY,
- self.PROV_PROBLEM_WITH_PROVIDER_KEY,
- self.PROV_UNSUPPORTED_CLIENT,
- self.PROV_UNSUPPORTED_API,
- self.PROV_CANCELLED_SETUP,
- self.PROV_GET_ALL_SERVICES,
- self.PROV_GET_SUPPORTED_SERVICES,
- self.PROV_GET_DETAILS,
- self.PROV_GET_PINNED_PROVIDERS,
-
- self.SRP_REGISTRATION_FINISHED,
- self.SRP_REGISTRATION_FAILED,
- self.SRP_REGISTRATION_TAKEN,
-
- self.EIP_CONFIG_READY,
- self.EIP_CLIENT_CERTIFICATE_READY,
- self.EIP_CANCELLED_SETUP,
-
- self.EIP_CONNECTED,
- self.EIP_DISCONNECTED,
- self.EIP_CONNECTION_DIED,
- self.EIP_CONNECTION_ABORTED,
- self.EIP_STOPPED,
-
- self.EIP_NO_POLKIT_AGENT_ERROR,
- self.EIP_NO_TUN_KEXT_ERROR,
- self.EIP_NO_PKEXEC_ERROR,
- self.EIP_OPENVPN_NOT_FOUND_ERROR,
- self.EIP_OPENVPN_ALREADY_RUNNING,
- self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING,
- self.EIP_VPN_LAUNCHER_EXCEPTION,
-
- self.EIP_GET_GATEWAYS_LIST,
- self.EIP_GET_GATEWAYS_LIST_ERROR,
- self.EIP_UNINITIALIZED_PROVIDER,
- self.EIP_GET_INITIALIZED_PROVIDERS,
-
- self.EIP_NETWORK_UNREACHABLE,
- self.EIP_PROCESS_RESTART_TLS,
- self.EIP_PROCESS_RESTART_PING,
-
- self.EIP_STATE_CHANGED,
- self.EIP_STATUS_CHANGED,
- self.EIP_PROCESS_FINISHED,
-
- self.EIP_CAN_START,
- self.EIP_CANNOT_START,
-
- self.EIP_DNS_OK,
- self.EIP_DNS_ERROR,
-
- self.SRP_AUTH_OK,
- self.SRP_AUTH_ERROR,
- self.SRP_AUTH_SERVER_ERROR,
- self.SRP_AUTH_CONNECTION_ERROR,
- self.SRP_AUTH_BAD_USER_OR_PASSWORD,
- self.SRP_LOGOUT_OK,
- self.SRP_LOGOUT_ERROR,
- self.SRP_PASSWORD_CHANGE_OK,
- self.SRP_PASSWORD_CHANGE_ERROR,
- self.SRP_PASSWORD_CHANGE_BADPW,
- self.SRP_NOT_LOGGED_IN_ERROR,
- self.SRP_STATUS_LOGGED_IN,
- self.SRP_STATUS_NOT_LOGGED_IN,
-
- self.SOLEDAD_BOOTSTRAP_FAILED,
- self.SOLEDAD_BOOTSTRAP_FINISHED,
- self.SOLEDAD_OFFLINE_FAILED,
- self.SOLEDAD_OFFLINE_FINISHED,
- self.SOLEDAD_INVALID_AUTH_TOKEN,
- self.SOLEDAD_CANCELLED_BOOTSTRAP,
-
- self.SOLEDAD_PASSWORD_CHANGE_OK,
- self.SOLEDAD_PASSWORD_CHANGE_ERROR,
-
- self.KEYMANAGER_EXPORT_OK,
- self.KEYMANAGER_EXPORT_ERROR,
- self.KEYMANAGER_KEYS_LIST,
-
- self.KEYMANAGER_IMPORT_IOERROR,
- self.KEYMANAGER_IMPORT_DATAMISMATCH,
- self.KEYMANAGER_IMPORT_MISSINGKEY,
- self.KEYMANAGER_IMPORT_ADDRESSMISMATCH,
- self.KEYMANAGER_IMPORT_OK,
- self.KEYMANAGER_KEY_DETAILS,
-
- self.IMAP_STOPPED,
-
- self.BACKEND_BAD_CALL,
- ]
-
- for sig in signals:
- self._signals[sig] = getattr(self, sig)
-
- def signal(self, key, data=None):
- """
- Emits a Qt signal based on the key provided, with the data if provided.
-
- :param key: string identifying the signal to emit
- :type key: str
- :param data: object to send with the data
- :type data: object
-
- NOTE: The data object will be a serialized str in the backend,
- and an unserialized object in the frontend, but for now we
- just care about objects.
- """
- # Right now it emits Qt signals. The backend version of this
- # will do zmq.send_multipart, and the frontend version will be
- # similar to this
-
- # for some reason emitting 'None' gives a segmentation fault.
- if data is None:
- data = ''
-
- try:
- self._signals[key].emit(data)
- except KeyError:
- logger.error("Unknown key for signal %s!" % (key,))
+ prov_cancelled_setup = QtCore.Signal()
+ prov_check_api_certificate = QtCore.Signal(object)
+ prov_check_ca_fingerprint = QtCore.Signal(object)
+ prov_download_ca_cert = QtCore.Signal(object)
+ prov_download_provider_info = QtCore.Signal(object)
+ prov_get_all_services = QtCore.Signal(object)
+ prov_get_details = QtCore.Signal(object)
+ prov_get_pinned_providers = QtCore.Signal(object)
+ prov_get_supported_services = QtCore.Signal(object)
+ prov_https_connection = QtCore.Signal(object)
+ prov_name_resolution = QtCore.Signal(object)
+ prov_problem_with_provider = QtCore.Signal()
+ prov_unsupported_api = QtCore.Signal()
+ prov_unsupported_client = QtCore.Signal()
+
+ soledad_bootstrap_failed = QtCore.Signal()
+ soledad_bootstrap_finished = QtCore.Signal()
+ soledad_cancelled_bootstrap = QtCore.Signal()
+ soledad_invalid_auth_token = QtCore.Signal()
+ soledad_offline_failed = QtCore.Signal()
+ soledad_offline_finished = QtCore.Signal()
+ soledad_password_change_error = QtCore.Signal()
+ soledad_password_change_ok = QtCore.Signal()
+
+ srp_auth_bad_user_or_password = QtCore.Signal()
+ srp_auth_connection_error = QtCore.Signal()
+ srp_auth_error = QtCore.Signal()
+ srp_auth_ok = QtCore.Signal()
+ srp_auth_server_error = QtCore.Signal()
+ srp_logout_error = QtCore.Signal()
+ srp_logout_ok = QtCore.Signal()
+ srp_not_logged_in_error = QtCore.Signal()
+ srp_password_change_badpw = QtCore.Signal()
+ srp_password_change_error = QtCore.Signal()
+ srp_password_change_ok = QtCore.Signal()
+ srp_registration_failed = QtCore.Signal()
+ srp_registration_finished = QtCore.Signal()
+ srp_registration_taken = QtCore.Signal()
+ srp_status_logged_in = QtCore.Signal()
+ srp_status_not_logged_in = QtCore.Signal()
diff --git a/src/leap/bitmask/backend/settings.py b/src/leap/bitmask/backend/settings.py
new file mode 100644
index 00000000..5cb4c616
--- /dev/null
+++ b/src/leap/bitmask/backend/settings.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# settings.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Backend settings
+"""
+import ConfigParser
+import logging
+import os
+
+from leap.bitmask.util import get_path_prefix
+from leap.common.check import leap_assert, leap_assert_type
+
+logger = logging.getLogger(__name__)
+
+# We need this one available for the default decorator
+GATEWAY_AUTOMATIC = "Automatic"
+GENERAL_SECTION = "General"
+
+
+class Settings(object):
+ """
+ Leap backend settings hanler.
+ """
+ CONFIG_NAME = "leap-backend.conf"
+
+ # keys
+ GATEWAY_KEY = "Gateway"
+
+ def __init__(self):
+ """
+ Create the ConfigParser object and read it.
+ """
+ self._settings_path = os.path.join(get_path_prefix(),
+ "leap", self.CONFIG_NAME)
+
+ self._settings = ConfigParser.ConfigParser()
+ self._settings.read(self._settings_path)
+
+ self._add_section(GENERAL_SECTION)
+
+ def _add_section(self, section):
+ """
+ Add `section` to the config file and don't fail if already exists.
+
+ :param section: the section to add.
+ :type section: str
+ """
+ self._settings.read(self._settings_path)
+ try:
+ self._settings.add_section(section)
+ except ConfigParser.DuplicateSectionError:
+ pass
+
+ def _save(self):
+ """
+ Save the current state to the config file.
+ """
+ with open(self._settings_path, 'wb') as f:
+ self._settings.write(f)
+
+ def _get_value(self, section, key, default):
+ """
+ Return the value for the fiven `key` in `section`.
+ If there's no such section/key, `default` is returned.
+
+ :param section: the section to get the value from.
+ :type section: str
+ :param key: the key which value we want to get.
+ :type key: str
+ :param default: the value to return if there is no section/key.
+ :type default: object
+
+ :rtype: object
+ """
+ try:
+ return self._settings.get(section, key)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+ return default
+
+ def get_selected_gateway(self, provider):
+ """
+ Return the configured gateway for the given `provider`.
+
+ :param provider: provider domain
+ :type provider: str
+
+ :rtype: str
+ """
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ return self._get_value(provider, self.GATEWAY_KEY, GATEWAY_AUTOMATIC)
+
+ def set_selected_gateway(self, provider, gateway):
+ """
+ Saves the configured gateway for the given provider
+
+ :param provider: provider domain
+ :type provider: str
+
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ leap_assert_type(gateway, (str, unicode))
+
+ self._add_section(provider)
+
+ self._settings.set(provider, self.GATEWAY_KEY, gateway)
+ self._save()
+
+ def get_uuid(self, username):
+ """
+ Gets the uuid for a given username.
+
+ :param username: the full user identifier in the form user@provider
+ :type username: basestring
+ """
+ leap_assert("@" in username,
+ "Expected username in the form user@provider")
+ user, provider = username.split('@')
+
+ return self._get_value(provider, username, "")
+
+ def set_uuid(self, username, value):
+ """
+ Sets the uuid for a given username.
+
+ :param username: the full user identifier in the form user@provider
+ :type username: str or unicode
+ :param value: the uuid to save or None to remove it
+ :type value: str or unicode or None
+ """
+ leap_assert("@" in username,
+ "Expected username in the form user@provider")
+ user, provider = username.split('@')
+
+ if value is None:
+ self._settings.remove_option(provider, username)
+ else:
+ leap_assert(len(value) > 0, "We cannot save an empty uuid")
+ self._add_section(provider)
+ self._settings.set(provider, username, value)
+
+ self._save()
diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py
new file mode 100644
index 00000000..574bfa71
--- /dev/null
+++ b/src/leap/bitmask/backend/signaler.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+# signaler.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Signaler client.
+Receives signals from the backend and sends to the signaling server.
+"""
+import Queue
+import threading
+import time
+
+import zmq
+
+from leap.bitmask.backend.api import SIGNALS
+from leap.bitmask.backend.utils import get_frontend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Signaler(object):
+ """
+ Signaler client.
+ Receives signals from the backend and sends to the signaling server.
+ """
+ PORT = "5667"
+ SERVER = "tcp://localhost:%s" % PORT
+ POLL_TIMEOUT = 2000 # ms
+ POLL_TRIES = 500
+
+ def __init__(self):
+ """
+ Initialize the ZMQ socket to talk to the signaling server.
+ """
+ context = zmq.Context()
+ logger.debug("Connecting to signaling server...")
+ socket = context.socket(zmq.REQ)
+
+ # public, secret = zmq.curve_keypair()
+ client_keys = zmq.curve_keypair()
+ socket.curve_publickey = client_keys[0]
+ socket.curve_secretkey = client_keys[1]
+
+ # The client must know the server's public key to make a CURVE
+ # connection.
+ public, _ = get_frontend_certificates()
+ socket.curve_serverkey = public
+
+ socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.connect(self.SERVER)
+ self._socket = socket
+
+ self._signal_queue = Queue.Queue()
+
+ self._do_work = threading.Event() # used to stop the worker thread.
+ self._worker_signaler = threading.Thread(target=self._worker)
+
+ def __getattribute__(self, name):
+ """
+ This allows the user to do:
+ S = Signaler()
+ S.SOME_SIGNAL
+
+ Just by having defined 'some_signal' in _SIGNALS
+
+ :param name: the attribute name that is requested.
+ :type name: str
+ """
+ if name in SIGNALS:
+ return name
+ else:
+ return object.__getattribute__(self, name)
+
+ def signal(self, signal, data=None):
+ """
+ Sends a signal to the signaling server.
+
+ :param signal: the signal to send.
+ :type signal: str
+ """
+ if signal not in SIGNALS:
+ raise Exception("Unknown signal: '{0}'".format(signal))
+
+ request = {
+ 'signal': signal,
+ 'data': data,
+ }
+
+ try:
+ request_json = zmq.utils.jsonapi.dumps(request)
+ except Exception as e:
+ msg = ("Error serializing request into JSON.\n"
+ "Exception: {0} Data: {1}")
+ msg = msg.format(e, request)
+ logger.critical(msg)
+ raise
+
+ # queue the call in order to handle the request in a thread safe way.
+ self._signal_queue.put(request_json)
+
+ def _worker(self):
+ """
+ Worker loop that processes the Queue of pending requests to do.
+ """
+ while self._do_work.is_set():
+ try:
+ request = self._signal_queue.get(block=False)
+ self._send_request(request)
+ except Queue.Empty:
+ pass
+ time.sleep(0.01)
+
+ logger.debug("Signaler thread stopped.")
+
+ def start(self):
+ """
+ Start the Signaler worker.
+ """
+ self._do_work.set()
+ self._worker_signaler.start()
+
+ def stop(self):
+ """
+ Stop the Signaler worker.
+ """
+ self._do_work.clear()
+
+ def _send_request(self, request):
+ """
+ Send the given request to the server.
+ This is used from a thread safe loop in order to avoid sending a
+ request without receiving a response from a previous one.
+
+ :param request: the request to send.
+ :type request: str
+ """
+ # logger.debug("Signaling '{0}'".format(request))
+ self._socket.send(request)
+
+ poll = zmq.Poller()
+ poll.register(self._socket, zmq.POLLIN)
+
+ reply = None
+ tries = 0
+
+ while True:
+ socks = dict(poll.poll(self.POLL_TIMEOUT))
+ if socks.get(self._socket) == zmq.POLLIN:
+ reply = self._socket.recv()
+ break
+
+ tries += 1
+ if tries < self.POLL_TRIES:
+ logger.warning('Retrying receive... {0}/{1}'.format(
+ tries, self.POLL_TRIES))
+ else:
+ break
+
+ if reply is None:
+ msg = "Timeout error contacting backend."
+ logger.critical(msg)
+ # else:
+ # msg = "Received reply for '{0}' -> '{1}'".format(request, reply)
+ # logger.debug(msg)
diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py
new file mode 100644
index 00000000..433f18ed
--- /dev/null
+++ b/src/leap/bitmask/backend/signaler_qt.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+# signaler_qt.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Signaling server.
+Receives signals from the signaling client and emit Qt signals for the GUI.
+"""
+import threading
+import time
+
+from PySide import QtCore
+
+import zmq
+from zmq.auth.thread import ThreadAuthenticator
+
+from leap.bitmask.backend.api import SIGNALS
+from leap.bitmask.backend.utils import get_frontend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class SignalerQt(QtCore.QObject):
+ """
+ Signaling server.
+ Receives signals from the signaling client and emit Qt signals for the GUI.
+ """
+ PORT = "5667"
+ BIND_ADDR = "tcp://127.0.0.1:%s" % PORT
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ # Note: we use a plain thread instead of a QThread since works better.
+ # The signaler was not responding on OSX if the worker loop was run in
+ # a QThread.
+ # Possibly, ZMQ was not getting cycles to do work because Qt not
+ # receiving focus or something.
+ self._worker_thread = threading.Thread(target=self._run)
+ self._do_work = threading.Event()
+
+ def start(self):
+ """
+ Start the worker thread for the signaler server.
+ """
+ self._do_work.set()
+ self._worker_thread.start()
+
+ def _run(self):
+ """
+ Start a loop to process the ZMQ requests from the signaler client.
+ """
+ logger.debug("Running SignalerQt loop")
+ context = zmq.Context()
+ socket = context.socket(zmq.REP)
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(context)
+ auth.start()
+ auth.allow('127.0.0.1')
+
+ # Tell authenticator to use the certificate in a directory
+ auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
+ public, secret = get_frontend_certificates()
+ socket.curve_publickey = public
+ socket.curve_secretkey = secret
+ socket.curve_server = True # must come before bind
+
+ socket.bind(self.BIND_ADDR)
+
+ while self._do_work.is_set():
+ # Wait for next request from client
+ try:
+ request = socket.recv(zmq.NOBLOCK)
+ # logger.debug("Received request: '{0}'".format(request))
+ socket.send("OK")
+ self._process_request(request)
+ except zmq.ZMQError as e:
+ if e.errno != zmq.EAGAIN:
+ raise
+ time.sleep(0.01)
+
+ logger.debug("SignalerQt thread stopped.")
+
+ def stop(self):
+ """
+ Stop the SignalerQt blocking loop.
+ """
+ self._do_work.clear()
+
+ def _process_request(self, request_json):
+ """
+ Process a request and call the according method with the given
+ parameters.
+
+ :param request_json: a json specification of a request.
+ :type request_json: str
+ """
+ try:
+ request = zmq.utils.jsonapi.loads(request_json)
+ signal = request['signal']
+ data = request['data']
+ except Exception as e:
+ msg = "Malformed JSON data in Signaler request '{0}'. Exc: {1!r}"
+ msg = msg.format(request_json, e)
+ logger.critical(msg)
+ raise
+
+ if signal not in SIGNALS:
+ logger.error("Unknown signal received, '{0}'".format(signal))
+ return
+
+ try:
+ qt_signal = getattr(self, signal)
+ except Exception:
+ logger.warning("Signal not implemented, '{0}'".format(signal))
+ return
+
+ # logger.debug("Emitting '{0}'".format(signal))
+ if data is None:
+ qt_signal.emit()
+ else:
+ qt_signal.emit(data)
diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py
new file mode 100644
index 00000000..65bf6753
--- /dev/null
+++ b/src/leap/bitmask/backend/utils.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# utils.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Backend utilities to handle ZMQ certificates.
+"""
+import os
+import shutil
+import stat
+
+import zmq.auth
+
+from leap.bitmask.util import get_path_prefix
+from leap.common.files import mkdir_p
+
+KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates')
+
+
+def generate_certificates():
+ """
+ Generate client and server CURVE certificate files.
+ """
+ # Create directory for certificates, remove old content if necessary
+ if os.path.exists(KEYS_DIR):
+ shutil.rmtree(KEYS_DIR)
+ mkdir_p(KEYS_DIR)
+ # set permissions to: 0700 (U:rwx G:--- O:---)
+ os.chmod(KEYS_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+
+ # create new keys in certificates dir
+ # public_file, secret_file = create_certificates(...)
+ zmq.auth.create_certificates(KEYS_DIR, "frontend")
+ zmq.auth.create_certificates(KEYS_DIR, "backend")
+
+
+def get_frontend_certificates():
+ """
+ Return the frontend's public and secret certificates.
+ """
+ frontend_secret_file = os.path.join(KEYS_DIR, "frontend.key_secret")
+ public, secret = zmq.auth.load_certificate(frontend_secret_file)
+ return public, secret
+
+
+def get_backend_certificates(base_dir='.'):
+ """
+ Return the backend's public and secret certificates.
+ """
+ backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret")
+ public, secret = zmq.auth.load_certificate(backend_secret_file)
+ return public, secret
diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py
index e69de29b..716ae4a7 100644
--- a/src/leap/bitmask/backend_app.py
+++ b/src/leap/bitmask/backend_app.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# backend_app.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Start point for the Backend.
+"""
+import logging
+import multiprocessing
+import signal
+
+from leap.bitmask.backend.leapbackend import LeapBackend
+from leap.bitmask.util import dict_to_flags
+
+logger = logging.getLogger(__name__)
+
+
+def signal_handler(signum, frame):
+ """
+ Signal handler that quits the running app cleanly.
+
+ :param signum: number of the signal received (e.g. SIGINT -> 2)
+ :type signum: int
+ :param frame: current stack frame
+ :type frame: frame or None
+ """
+ # Note: we don't stop the backend in here since the frontend signal handler
+ # will take care of that.
+ # In the future we may need to do the stop in here when the frontend and
+ # the backend are run separately (without multiprocessing)
+ pname = multiprocessing.current_process().name
+ logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
+
+
+def run_backend(bypass_checks, flags_dict, frontend_pid=None):
+ """
+ Run the backend for the application.
+
+ :param bypass_checks: whether we should bypass the checks or not
+ :type bypass_checks: bool
+ :param flags_dict: a dict containing the flag values set on app start.
+ :type flags_dict: dict
+ """
+ # ignore SIGINT since app.py takes care of signaling SIGTERM to us.
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ dict_to_flags(flags_dict)
+
+ backend = LeapBackend(bypass_checks=bypass_checks,
+ frontend_pid=frontend_pid)
+ backend.run()
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index 2f3fdde4..6b70659d 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -55,5 +55,3 @@ OPENVPN_VERBOSITY = 1
# Skip the checks in the wizard, use for testing purposes only!
SKIP_WIZARD_CHECKS = False
-
-CURRENT_VPN_COUNTRY = None
diff --git a/src/leap/bitmask/config/provider_spec.py b/src/leap/bitmask/config/provider_spec.py
index cf942c7b..a1d91b90 100644
--- a/src/leap/bitmask/config/provider_spec.py
+++ b/src/leap/bitmask/config/provider_spec.py
@@ -37,7 +37,7 @@ leap_provider_spec = {
'default': {u'en': u'Test Provider'}
},
'description': {
- #'type': LEAPTranslatable,
+ # 'type': LEAPTranslatable,
'type': dict,
'format': 'translatable',
'default': {u'en': u'Test provider'}
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index 192a9d5c..d59b3c31 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -24,7 +24,7 @@ import requests
import srp
import json
-#this error is raised from requests
+# this error is raised from requests
from simplejson.decoder import JSONDecodeError
from functools import partial
from requests.adapters import HTTPAdapter
@@ -32,7 +32,7 @@ from requests.adapters import HTTPAdapter
from twisted.internet import threads
from twisted.internet.defer import CancelledError
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings
from leap.bitmask.util import request_helpers as reqhelper
from leap.bitmask.util.compat import requests_has_max_retries
from leap.bitmask.util.constants import REQUEST_TIMEOUT
@@ -151,7 +151,7 @@ class SRPAuth(object):
self._provider_config = provider_config
self._signaler = signaler
- self._settings = LeapSettings()
+ self._settings = Settings()
# **************************************************** #
# Dependency injection helpers, override this for more
@@ -523,7 +523,7 @@ class SRPAuth(object):
Password change callback.
"""
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_OK)
+ self._signaler.signal(self._signaler.srp_password_change_ok)
def _change_password_error(self, failure):
"""
@@ -535,9 +535,9 @@ class SRPAuth(object):
return
if failure.check(SRPAuthBadUserOrPassword):
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_BADPW)
+ self._signaler.signal(self._signaler.srp_password_change_badpw)
else:
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_ERROR)
+ self._signaler.signal(self._signaler.srp_password_change_error)
def authenticate(self, username, password):
"""
@@ -591,7 +591,7 @@ class SRPAuth(object):
:type _: IGNORED
"""
logger.debug("Successful login!")
- self._signaler.signal(self._signaler.SRP_AUTH_OK)
+ self._signaler.signal(self._signaler.srp_auth_ok)
def _authenticate_error(self, failure):
"""
@@ -612,13 +612,13 @@ class SRPAuth(object):
return
if failure.check(SRPAuthBadUserOrPassword):
- signal = self._signaler.SRP_AUTH_BAD_USER_OR_PASSWORD
+ signal = self._signaler.srp_auth_bad_user_or_password
elif failure.check(SRPAuthConnectionError):
- signal = self._signaler.SRP_AUTH_CONNECTION_ERROR
+ signal = self._signaler.srp_auth_connection_error
elif failure.check(SRPAuthenticationError):
- signal = self._signaler.SRP_AUTH_SERVER_ERROR
+ signal = self._signaler.srp_auth_server_error
else:
- signal = self._signaler.SRP_AUTH_ERROR
+ signal = self._signaler.srp_auth_error
self._signaler.signal(signal)
@@ -647,7 +647,7 @@ class SRPAuth(object):
logger.warning("Something went wrong with the logout: %r" %
(e,))
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_LOGOUT_ERROR)
+ self._signaler.signal(self._signaler.srp_logout_error)
raise
else:
self.set_session_id(None)
@@ -657,7 +657,7 @@ class SRPAuth(object):
self._session = self._fetcher.session()
logger.debug("Successfully logged out.")
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_LOGOUT_OK)
+ self._signaler.signal(self._signaler.srp_logout_ok)
def set_session_id(self, session_id):
with self._session_id_lock:
diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py
index f03dc469..86510de1 100644
--- a/src/leap/bitmask/crypto/srpregister.py
+++ b/src/leap/bitmask/crypto/srpregister.py
@@ -179,11 +179,11 @@ class SRPRegister(QtCore.QObject):
return
if status_code in self.STATUS_OK:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FINISHED)
+ self._signaler.signal(self._signaler.srp_registration_finished)
elif status_code == self.STATUS_TAKEN:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_TAKEN)
+ self._signaler.signal(self._signaler.srp_registration_taken)
else:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED)
+ self._signaler.signal(self._signaler.srp_registration_failed)
if __name__ == "__main__":
diff --git a/src/leap/bitmask/crypto/tests/fake_provider.py b/src/leap/bitmask/crypto/tests/fake_provider.py
index b8cdbb12..60a3ef0a 100755
--- a/src/leap/bitmask/crypto/tests/fake_provider.py
+++ b/src/leap/bitmask/crypto/tests/fake_provider.py
@@ -156,7 +156,7 @@ def getSession(self, sessionInterface=None):
put the right cookie name in place
"""
if not self.session:
- #cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
+ # cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
cookiename = b"_".join([b'_session_id'] + self.sitepath)
sessionCookie = self.getCookie(cookiename)
if sessionCookie:
@@ -321,7 +321,7 @@ class OpenSSLServerContextFactory(object):
Create an SSL context.
"""
ctx = SSL.Context(SSL.SSLv23_METHOD)
- #ctx = SSL.Context(SSL.TLSv1_METHOD)
+ # ctx = SSL.Context(SSL.TLSv1_METHOD)
ctx.use_certificate_file(where('leaptestscert.pem'))
ctx.use_privatekey_file(where('leaptestskey.pem'))
diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py
index e69de29b..5ea89fc9 100644
--- a/src/leap/bitmask/frontend_app.py
+++ b/src/leap/bitmask/frontend_app.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# frontend_app.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Start point for the Frontend.
+"""
+import multiprocessing
+import signal
+import sys
+import os
+
+from functools import partial
+
+from PySide import QtCore, QtGui
+
+from leap.bitmask.config import flags
+from leap.bitmask.gui.mainwindow import MainWindow
+from leap.bitmask.util import dict_to_flags
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+def signal_handler(window, pid, signum, frame):
+ """
+ Signal handler that quits the running app cleanly.
+
+ :param window: a window with a `quit` callable
+ :type window: MainWindow
+ :param pid: process id of the main process.
+ :type pid: int
+ :param signum: number of the signal received (e.g. SIGINT -> 2)
+ :type signum: int
+ :param frame: current stack frame
+ :type frame: frame or None
+ """
+ my_pid = os.getpid()
+ if pid == my_pid:
+ pname = multiprocessing.current_process().name
+ logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
+ window.quit()
+
+
+def run_frontend(options, flags_dict, backend_pid):
+ """
+ Run the GUI for the application.
+
+ :param options: a dict of options parsed from the command line.
+ :type options: dict
+ :param flags_dict: a dict containing the flag values set on app start.
+ :type flags_dict: dict
+ """
+ dict_to_flags(flags_dict)
+
+ start_hidden = options["start_hidden"]
+
+ # 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
+ if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None:
+ sys.argv.append("-style")
+ sys.argv.append("Cleanlooks")
+
+ qApp = QtGui.QApplication(sys.argv)
+
+ # To test:
+ # $ LANG=es ./app.py
+ locale = QtCore.QLocale.system().name()
+ qtTranslator = QtCore.QTranslator()
+ if qtTranslator.load("qt_%s" % locale, ":/translations"):
+ qApp.installTranslator(qtTranslator)
+ appTranslator = QtCore.QTranslator()
+ if appTranslator.load("%s.qm" % locale[:2], ":/translations"):
+ qApp.installTranslator(appTranslator)
+
+ # Needed for initializing qsettings it will write
+ # .config/leap/leap.conf top level app settings in a platform
+ # independent way
+ qApp.setOrganizationName("leap")
+ qApp.setApplicationName("leap")
+ qApp.setOrganizationDomain("leap.se")
+
+ # HACK:
+ # We need to do some 'python work' once in a while, otherwise, no python
+ # code will be called and the Qt event loop will prevent the signal
+ # handlers for SIGINT/SIGTERM to be called.
+ # see: http://stackoverflow.com/a/4939113/687989
+ timer = QtCore.QTimer()
+ timer.start(500) # You may change this if you wish.
+ timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms.
+
+ window = MainWindow(start_hidden=start_hidden, backend_pid=backend_pid)
+
+ my_pid = os.getpid()
+ sig_handler = partial(signal_handler, window, my_pid)
+ signal.signal(signal.SIGINT, sig_handler)
+ signal.signal(signal.SIGTERM, sig_handler)
+
+ sys.exit(qApp.exec_())
+
+
+if __name__ == '__main__':
+ run_frontend()
diff --git a/src/leap/bitmask/gui/__init__.py b/src/leap/bitmask/gui/__init__.py
index 4b289442..bba02061 100644
--- a/src/leap/bitmask/gui/__init__.py
+++ b/src/leap/bitmask/gui/__init__.py
@@ -17,5 +17,8 @@
"""
init file for leap.gui
"""
-app = __import__("app", globals(), locals(), [], 2)
-__all__ = [app]
+# This was added for coverage and testing, but when doing the osx
+# bundle with py2app it fails because of this, so commenting for now
+# Also creates conflicts with the new frontend/backend separation.
+# app = __import__("app", globals(), locals(), [], 2)
+# __all__ = [app]
diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py
index 530cd38d..0f63972f 100644
--- a/src/leap/bitmask/gui/eip_preferenceswindow.py
+++ b/src/leap/bitmask/gui/eip_preferenceswindow.py
@@ -33,7 +33,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
"""
Window that displays the EIP preferences.
"""
- def __init__(self, parent, domain, backend):
+ def __init__(self, parent, domain, backend, leap_signaler):
"""
:param parent: parent object of the EIPPreferencesWindow.
:type parent: QWidget
@@ -46,6 +46,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._settings = LeapSettings()
+ self._leap_signaler = leap_signaler
self._backend = backend
# Load UI
@@ -96,7 +97,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
if not providers:
return
- self._backend.eip_get_initialized_providers(providers)
+ self._backend.eip_get_initialized_providers(domains=providers)
@QtCore.Slot(list)
def _load_providers_in_combo(self, providers):
@@ -148,6 +149,8 @@ class EIPPreferencesWindow(QtGui.QDialog):
gateway = self.ui.cbGateways.itemData(idx)
self._settings.set_selected_gateway(provider, gateway)
+ self._backend.settings_set_selected_gateway(provider=provider,
+ gateway=gateway)
msg = self.tr(
"Gateway settings for provider '{0}' saved.").format(provider)
@@ -174,7 +177,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
domain = self.ui.cbProvidersGateway.itemData(domain_idx)
self._selected_domain = domain
- self._backend.eip_get_gateways_list(domain)
+ self._backend.eip_get_gateways_list(domain=domain)
@QtCore.Slot(list)
def _update_gateways_list(self, gateways):
@@ -248,7 +251,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.ui.cbGateways.setEnabled(False)
def _backend_connect(self):
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.eip_get_gateways_list.connect(self._update_gateways_list)
sig.eip_get_gateways_list_error.connect(self._gateways_list_error)
sig.eip_uninitialized_provider.connect(
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index bd569343..1e764a8c 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -24,7 +24,6 @@ from functools import partial
from PySide import QtCore, QtGui
-from leap.bitmask.config import flags
from leap.bitmask.services import get_service_display_name, EIP_SERVICE
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.util.averages import RateMovingAverage
@@ -44,7 +43,7 @@ class EIPStatusWidget(QtGui.QWidget):
RATE_STR = "%1.2f KB/s"
TOTAL_STR = "%1.2f Kb"
- def __init__(self, parent=None, eip_conductor=None):
+ def __init__(self, parent, eip_conductor, leap_signaler):
"""
:param parent: the parent of the widget.
:type parent: QObject
@@ -60,6 +59,8 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui = Ui_EIPStatus()
self.ui.setupUi(self)
+ self._leap_signaler = leap_signaler
+
self.eip_conductor = eip_conductor
self.eipconnection = eip_conductor.eip_connection
@@ -69,8 +70,11 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui.eip_bandwidth.hide()
self.hide_fw_down_button()
+ self.hide_eip_cancel_button()
self.ui.btnFwDown.clicked.connect(
self._on_fw_down_button_clicked)
+ self.ui.btnEipCancel.clicked.connect(
+ self._on_eip_cancel_button_clicked)
# Set the EIP status icons
self.CONNECTING_ICON = None
@@ -87,6 +91,7 @@ class EIPStatusWidget(QtGui.QWidget):
self._provider = ""
self.is_restart = False
self.is_cold_start = True
+ self.user_cancelled = False
self.missing_helpers = False
@@ -98,7 +103,7 @@ class EIPStatusWidget(QtGui.QWidget):
"""
Connect backend signals.
"""
- signaler = self.eip_conductor._backend.signaler
+ signaler = self._leap_signaler
signaler.eip_openvpn_already_running.connect(
self._on_eip_openvpn_already_running)
@@ -121,8 +126,8 @@ class EIPStatusWidget(QtGui.QWidget):
# XXX we cannot connect this signal now because
# it interferes with the proper notifications during restarts
# without available network.
- #signaler.eip_network_unreachable.connect(
- #self._on_eip_network_unreachable)
+ # signaler.eip_network_unreachable.connect(
+ # self._on_eip_network_unreachable)
def _make_status_clickable(self):
"""
@@ -284,6 +289,8 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered when the app activates eip.
Disables the start/stop button.
"""
+ # XXX hack -- we show the cancel button instead.
+ self.ui.btnEipStartStop.hide()
self.set_startstop_enabled(False)
msg = self.tr("Encrypted Internet is starting")
self.set_eip_message(msg)
@@ -294,6 +301,9 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered when a default provider_config has not been found.
Disables the start button and adds instructions to the user.
"""
+ # XXX this name is unfortunate. "disable" is also applied to a
+ # pushbutton being grayed out.
+
logger.debug('Hiding EIP start button')
# you might be tempted to change this for a .setEnabled(False).
# it won't work. it's under the claws of the state machine.
@@ -324,8 +334,9 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered after a successful login.
Enables the start button.
"""
- #logger.debug('Showing EIP start button')
+ # logger.debug('Showing EIP start button')
self.eip_button.show()
+ self.hide_eip_cancel_button()
# Restore the eip action menu
menu = self._systray.contextMenu()
@@ -356,6 +367,7 @@ class EIPStatusWidget(QtGui.QWidget):
leap_assert_type(error, bool)
if error:
logger.error(status)
+ self.hide_eip_cancel_button()
else:
logger.debug(status)
self._eip_status = status
@@ -419,6 +431,28 @@ class EIPStatusWidget(QtGui.QWidget):
self.set_eip_message(msg)
self.set_eip_status("")
+ def hide_eip_cancel_button(self):
+ """
+ Hide eip-cancel button.
+ """
+ self.ui.btnEipCancel.hide()
+
+ def show_eip_cancel_button(self):
+ """
+ Show eip-cancel button.
+ """
+ self.ui.btnEipCancel.show()
+ self.user_cancelled = False
+
+ def _on_eip_cancel_button_clicked(self):
+ """
+ Call backend to kill the openvpn process with root privileges.
+ """
+ self.eip_conductor.cancelled = True
+ self.eip_conductor._backend.bitmask_root_vpn_down()
+ self.user_cancelled = True
+ self.hide_eip_cancel_button()
+
@QtCore.Slot(dict)
def eip_stopped(self, restart=False, failed=False):
"""
@@ -432,6 +466,11 @@ class EIPStatusWidget(QtGui.QWidget):
self._reset_traffic_rates()
self.ui.eip_bandwidth.hide()
+ if self.user_cancelled:
+ self.eip_conductor._backend.tear_fw_down()
+ self.eip_button.show()
+ failed = False
+
# This is assuming the firewall works correctly, but we should test fw
# status positively.
# Or better call it from the conductor...
@@ -446,6 +485,7 @@ class EIPStatusWidget(QtGui.QWidget):
msg = failed_msg
else:
msg = clear_traffic
+
self.set_eip_message(msg)
self.ui.lblEIPStatus.show()
self.show()
@@ -522,16 +562,19 @@ class EIPStatusWidget(QtGui.QWidget):
self.eipconnection.qtsigs.connected_signal.emit()
self._on_eip_connected()
self.is_cold_start = False
+ self.hide_eip_cancel_button()
+ self.eip_button.show()
# XXX should lookup vpn_state map in EIPConnection
elif vpn_state == "AUTH":
self.set_eip_status(self.tr("Authenticating..."))
+
+ # XXX should be handled by a future state machine instead.
+ self.show_eip_cancel_button()
# we wipe up any previous error info in the EIP message
# when we detect vpn authentication is happening
msg = self.tr("Encrypted Internet is starting")
self.set_eip_message(msg)
- # on the first-run path, we hadn't showed the button yet.
- self.eip_button.show()
elif vpn_state == "GET_CONFIG":
self.set_eip_status(self.tr("Retrieving configuration..."))
elif vpn_state == "WAIT":
@@ -543,7 +586,7 @@ class EIPStatusWidget(QtGui.QWidget):
elif vpn_state == "ALREADYRUNNING":
# Put the following calls in Qt's event queue, otherwise
# the UI won't update properly
- #self.send_disconnect_signal()
+ # self.send_disconnect_signal()
QtDelayedCall(
0, self.eipconnection.qtsigns.do_disconnect_signal.emit)
msg = self.tr("Unable to start VPN, it's already running.")
@@ -587,16 +630,23 @@ class EIPStatusWidget(QtGui.QWidget):
self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
self._eip_status_menu.setTitle(tray_message)
- def set_provider(self, provider):
+ def set_provider(self, provider, country_code):
+ """
+ Set the provider used right now, name and flag (if available).
+
+ :param provider: the provider in use.
+ :type provider: str
+ :param country_code: the country code of the gateway in use.
+ :type country_code: str
+ """
self._provider = provider
self.ui.lblEIPMessage.setText(
self.tr("Routing traffic through: <b>{0}</b>").format(
provider))
- ccode = flags.CURRENT_VPN_COUNTRY
- if ccode is not None:
- self.set_country_code(ccode)
+ if country_code is not None:
+ self.set_country_code(country_code)
def set_country_code(self, code):
"""
@@ -674,9 +724,17 @@ class EIPStatusWidget(QtGui.QWidget):
def _on_eip_vpn_launcher_exception(self):
# XXX We should implement again translatable exceptions so
# we can pass a translatable string to the panel (usermessage attr)
- self.set_eip_status("VPN Launcher error.", error=True)
+ # FIXME this logic should belong to the backend, not to this
+ # widget.
self.set_eipstatus_off()
+ st = self.tr("VPN Launcher error. See the logs for more info.")
+ self.set_eip_status(st, error=True)
+
+ msg = self.tr("Encrypted Internet failed to start")
+ self.set_eip_message(msg)
+ self.show_fw_down_button()
+
self.aborted()
def _on_eip_no_polkit_agent_error(self):
@@ -729,7 +787,7 @@ class EIPStatusWidget(QtGui.QWidget):
self.set_eip_status_icon("error")
def set_eipstatus_off(self, error=True):
- # XXX this should be handled by the state machine.
+ # XXX this should be handled by the state machine.
"""
Sets eip status to off
"""
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index 3a8354b1..360dd5f0 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -18,11 +18,10 @@
"""
History log window
"""
-import logging
import cgi
+import logging
from PySide import QtCore, QtGui
-from twisted.internet import threads
from ui_loggerwindow import Ui_LoggerWindow
@@ -38,17 +37,17 @@ class LoggerWindow(QtGui.QDialog):
"""
Window that displays a history of the logged messages in the app.
"""
- def __init__(self, handler):
+ _paste_ok = QtCore.Signal(object)
+ _paste_error = QtCore.Signal(object)
+
+ def __init__(self, parent, handler):
"""
Initialize the widget with the custom handler.
:param handler: Custom handler that supports history and signal.
:type handler: LeapLogHandler.
"""
- from twisted.internet import reactor
- self.reactor = reactor
-
- QtGui.QDialog.__init__(self)
+ QtGui.QDialog.__init__(self, parent)
leap_assert(handler, "We need a handler for the logger window")
leap_assert_type(handler, LeapLogHandler)
@@ -67,6 +66,9 @@ class LoggerWindow(QtGui.QDialog):
self.ui.cbCaseInsensitive.stateChanged.connect(self._load_history)
self.ui.btnPastebin.clicked.connect(self._pastebin_this)
+ self._paste_ok.connect(self._pastebin_ok)
+ self._paste_error.connect(self._pastebin_err)
+
self._current_filter = ""
self._current_history = ""
@@ -193,6 +195,45 @@ class LoggerWindow(QtGui.QDialog):
self.ui.btnPastebin.setText(self.tr("Send to Pastebin.com"))
self.ui.btnPastebin.setEnabled(True)
+ def _pastebin_ok(self, link):
+ """
+ Handle a successful paste.
+
+ :param link: the recently created pastebin link.
+ :type link: str
+ """
+ self._set_pastebin_sending(False)
+ msg = self.tr("Your pastebin link <a href='{0}'>{0}</a>")
+ msg = msg.format(link)
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
+
+ def _pastebin_err(self, failure):
+ """
+ Handle a failure in paste.
+
+ :param failure: the exception that made the paste fail.
+ :type failure: Exception
+ """
+ self._set_pastebin_sending(False)
+ logger.error(repr(failure))
+
+ msg = self.tr("Sending logs to Pastebin failed!")
+ if isinstance(failure, pastebin.PostLimitError):
+ msg = self.tr('Maximum posts per day reached')
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
+
def _pastebin_this(self):
"""
Send the current log history to pastebin.com and gives the user a link
@@ -204,55 +245,19 @@ class LoggerWindow(QtGui.QDialog):
"""
content = self._current_history
pb = pastebin.PastebinAPI()
- link = pb.paste(PASTEBIN_API_DEV_KEY, content,
- paste_name="Bitmask log",
- paste_expire_date='1M')
-
- # convert to 'raw' link
- link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1]
-
- return link
-
- def pastebin_ok(link):
- """
- Callback handler for `do_pastebin`.
-
- :param link: the recently created pastebin link.
- :type link: str
- """
- self._set_pastebin_sending(False)
- msg = self.tr("Your pastebin link <a href='{0}'>{0}</a>")
- msg = msg.format(link)
-
- # We save the dialog in an instance member to avoid dialog being
- # deleted right after we exit this method
- self._msgBox = msgBox = QtGui.QMessageBox(
- QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg)
- msgBox.setWindowModality(QtCore.Qt.NonModal)
- msgBox.show()
-
- def pastebin_err(failure):
- """
- Errback handler for `do_pastebin`.
-
- :param failure: the failure that triggered the errback.
- :type failure: twisted.python.failure.Failure
- """
- self._set_pastebin_sending(False)
- logger.error(repr(failure))
-
- msg = self.tr("Sending logs to Pastebin failed!")
- if failure.check(pastebin.PostLimitError):
- msg = self.tr('Maximum posts per day reached')
+ try:
+ link = pb.paste(PASTEBIN_API_DEV_KEY, content,
+ paste_name="Bitmask log",
+ paste_expire_date='1M')
+ # convert to 'raw' link
+ link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1]
- # We save the dialog in an instance member to avoid dialog being
- # deleted right after we exit this method
- self._msgBox = msgBox = QtGui.QMessageBox(
- QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg)
- msgBox.setWindowModality(QtCore.Qt.NonModal)
- msgBox.show()
+ self._paste_ok.emit(link)
+ except Exception as e:
+ self._paste_error.emit(e)
self._set_pastebin_sending(True)
- d = threads.deferToThread(do_pastebin)
- d.addCallback(pastebin_ok)
- d.addErrback(pastebin_err)
+
+ self._paste_thread = QtCore.QThread()
+ self._paste_thread.run = lambda: do_pastebin()
+ self._paste_thread.start()
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index baf29c23..8e0a3a15 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -216,8 +216,7 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnUser.setEnabled(enabled)
self.ui.lnPassword.setEnabled(enabled)
- if has_keyring():
- self.ui.chkRemember.setEnabled(enabled)
+ self.ui.chkRemember.setEnabled(enabled and has_keyring())
self.ui.cmbProviders.setEnabled(enabled)
self._set_cancel(not enabled)
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index 5caef745..bb755b5c 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -304,7 +304,7 @@ class MailStatusWidget(QtGui.QWidget):
ext_status = ""
if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY:
- ext_status = self.tr("Looking for key for this user")
+ ext_status = self.tr("Initial sync in progress, please wait...")
elif req.event == proto.KEYMANAGER_KEY_FOUND:
ext_status = self.tr("Found key! Starting mail...")
# elif req.event == proto.KEYMANAGER_KEY_NOT_FOUND:
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index a7e35d15..389ff172 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -18,13 +18,21 @@
Main window for Bitmask.
"""
import logging
+import time
from datetime import datetime
+import psutil
+
from PySide import QtCore, QtGui
from leap.bitmask import __version__ as VERSION
from leap.bitmask import __version_hash__ as VERSION_HASH
+
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
+
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
@@ -37,20 +45,20 @@ from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.systray import SysTray
from leap.bitmask.gui.wizard import Wizard
-from leap.bitmask.gui import twisted_main
from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
from leap.bitmask.platform_init.initializers import init_platform
from leap.bitmask.platform_init.initializers import init_signals
-from leap.bitmask.backend import leapbackend
+from leap.bitmask.backend.backend_proxy import BackendProxy
+from leap.bitmask.backend.leapsignaler import LeapSignaler
from leap.bitmask.services.eip import conductor as eip_conductor
from leap.bitmask.services.mail import conductor as mail_conductor
from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
-from leap.bitmask.util import make_address
+from leap.bitmask.util import autostart, make_address
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.logs.leap_log_handler import LeapLogHandler
@@ -89,20 +97,18 @@ class MainWindow(QtGui.QMainWindow):
EIP_START_TIMEOUT = 60000 # in milliseconds
# We give the services some time to a halt before forcing quit.
- SERVICES_STOP_TIMEOUT = 20000 # in milliseconds
+ SERVICES_STOP_TIMEOUT = 3000 # in milliseconds
- def __init__(self, bypass_checks=False, start_hidden=False):
+ def __init__(self, start_hidden=False, backend_pid=None):
"""
Constructor for the client main window
- :param bypass_checks: Set to true if the app should bypass first round
- of checks for CA certificates at bootstrap
- :type bypass_checks: bool
:param start_hidden: Set to true if the app should not show the window
but just the tray.
:type start_hidden: bool
"""
QtGui.QMainWindow.__init__(self)
+ autostart.set_autostart(True)
# register leap events ########################################
register(signal=proto.UPDATER_NEW_UPDATES,
@@ -119,15 +125,23 @@ class MainWindow(QtGui.QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.menuBar().setNativeMenuBar(not IS_LINUX)
- self._backend = leapbackend.Backend(bypass_checks)
- self._backend.start()
+
+ self._backend = BackendProxy()
+
+ # periodically check if the backend is alive
+ self._backend_checker = QtCore.QTimer(self)
+ self._backend_checker.timeout.connect(self._check_backend_status)
+ self._backend_checker.start(2000)
+
+ self._leap_signaler = LeapSignaler()
+ self._leap_signaler.start()
self._settings = LeapSettings()
+ # gateway = self._settings.get_selected_gateway(provider)
+ # self._backend.settings_set_selected_gateway(provider, gateway)
# Login Widget
- self._login_widget = LoginWidget(
- self._settings,
- self)
+ self._login_widget = LoginWidget(self._settings, self)
self.ui.loginLayout.addWidget(self._login_widget)
# Mail Widget
@@ -144,13 +158,17 @@ class MainWindow(QtGui.QMainWindow):
# EIP Control redux #########################################
self._eip_conductor = eip_conductor.EIPConductor(
- self._settings, self._backend)
- self._eip_status = EIPStatusWidget(self, self._eip_conductor)
+ self._settings, self._backend, self._leap_signaler)
+ self._eip_status = EIPStatusWidget(self, self._eip_conductor,
+ self._leap_signaler)
init_signals.eip_missing_helpers.connect(
self._disable_eip_missing_helpers)
self.ui.eipLayout.addWidget(self._eip_status)
+
+ # XXX we should get rid of the circular refs
+ # conductor <-> status, right now keeping state on the widget ifself.
self._eip_conductor.add_eip_widget(self._eip_status)
self._eip_conductor.connect_signals()
@@ -165,6 +183,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_needs_login.connect(self._eip_status.disable_eip_start)
self.eip_needs_login.connect(self._disable_eip_start_action)
+ # XXX all this info about state should move to eip conductor too
self._already_started_eip = False
self._trying_to_start_eip = False
@@ -180,7 +199,9 @@ class MainWindow(QtGui.QMainWindow):
self._services_being_stopped = {}
# used to know if we are in the final steps of quitting
+ self._quitting = False
self._finally_quitting = False
+ self._system_quit = False
self._backend_connected_signals = []
self._backend_connect()
@@ -249,17 +270,15 @@ class MainWindow(QtGui.QMainWindow):
# XXX should connect to mail_conductor.start_mail_service instead
self.soledad_ready.connect(self._start_smtp_bootstrapping)
self.soledad_ready.connect(self._start_imap_service)
- ################################# end Qt Signals connection ########
+ # ################################ end Qt Signals connection ########
init_platform()
self._wizard = None
self._wizard_firstrun = False
- self._logger_window = None
-
- self._bypass_checks = bypass_checks
self._start_hidden = start_hidden
+ self._backend_pid = backend_pid
self._mail_conductor = mail_conductor.MailConductor(self._backend)
self._mail_conductor.connect_mail_signals(self._mail_status)
@@ -283,7 +302,7 @@ class MainWindow(QtGui.QMainWindow):
self._wizard_firstrun = True
self._disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
- bypass_checks=bypass_checks)
+ leap_signaler=self._leap_signaler)
# Give this window time to finish init and then show the wizard
QtDelayedCall(1, self._launch_wizard)
self._wizard.accepted.connect(self._finish_init)
@@ -327,6 +346,23 @@ class MainWindow(QtGui.QMainWindow):
logger.error("Bad call to the backend:")
logger.error(data)
+ @QtCore.Slot()
+ def _check_backend_status(self):
+ """
+ TRIGGERS:
+ self._backend_checker.timeout
+
+ Check that the backend is running. Otherwise show an error to the user.
+ """
+ online = self._backend.online
+ if not online:
+ logger.critical("Backend is not online.")
+ QtGui.QMessageBox.critical(
+ self, self.tr("Application error"),
+ self.tr("There is a problem contacting the backend, please "
+ "restart Bitmask."))
+ self._backend_checker.stop()
+
def _backend_connect(self, only_tracked=False):
"""
Connect to backend signals.
@@ -342,7 +378,7 @@ class MainWindow(QtGui.QMainWindow):
that we are tracking to disconnect later.
:type only_tracked: bool
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
conntrack = self._connect_and_track
auth_err = self._authentication_error
@@ -407,6 +443,9 @@ class MainWindow(QtGui.QMainWindow):
sig.eip_dns_error.connect(self._eip_dns_error)
+ sig.eip_get_gateway_country_code.connect(self._set_eip_provider)
+ sig.eip_no_gateway.connect(self._set_eip_provider)
+
# ==================================================================
# Soledad signals
@@ -480,7 +519,7 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard is None:
self._disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
- bypass_checks=self._bypass_checks)
+ leap_signaler=self._leap_signaler)
self._wizard.accepted.connect(self._finish_init)
self._wizard.rejected.connect(self._rejected_wizard)
@@ -524,20 +563,16 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.ui.action_show_logs.triggered
- Displays the window with the history of messages logged until now
+ Display the window with the history of messages logged until now
and displays the new ones on arrival.
"""
- if self._logger_window is None:
- leap_log_handler = self._get_leap_logging_handler()
- if leap_log_handler is None:
- logger.error('Leap logger handler not found')
- return
- else:
- self._logger_window = LoggerWindow(handler=leap_log_handler)
- self._logger_window.setVisible(
- not self._logger_window.isVisible())
+ leap_log_handler = self._get_leap_logging_handler()
+ if leap_log_handler is None:
+ logger.error('Leap logger handler not found')
+ return
else:
- self._logger_window.setVisible(not self._logger_window.isVisible())
+ lw = LoggerWindow(self, handler=leap_log_handler)
+ lw.show()
@QtCore.Slot()
def _show_AKM(self):
@@ -545,7 +580,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.ui.action_advanced_key_management.triggered
- Displays the Advanced Key Management dialog.
+ Display the Advanced Key Management dialog.
"""
domain = self._login_widget.get_selected_provider()
logged_user = "{0}@{1}".format(self._logged_user, domain)
@@ -567,7 +602,7 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnPreferences.clicked (disabled for now)
self.ui.action_preferences
- Displays the preferences window.
+ Display the preferences window.
"""
user = self._logged_user
domain = self._login_widget.get_selected_provider()
@@ -575,7 +610,8 @@ class MainWindow(QtGui.QMainWindow):
if self._provider_details is not None:
mx_provided = MX_SERVICE in self._provider_details['services']
preferences = PreferencesWindow(self, user, domain, self._backend,
- self._soledad_started, mx_provided)
+ self._soledad_started, mx_provided,
+ self._leap_signaler)
self.soledad_ready.connect(preferences.set_soledad_ready)
preferences.show()
@@ -604,12 +640,13 @@ class MainWindow(QtGui.QMainWindow):
return
self._trying_to_start_eip = settings.get_autostart_eip()
- self._backend.eip_can_start(default_provider)
+ self._backend.eip_can_start(domain=default_provider)
# If we don't want to start eip, we leave everything
# initialized to quickly start it
if not self._trying_to_start_eip:
- self._backend.eip_setup(default_provider, skip_network=True)
+ self._backend.eip_setup(provider=default_provider,
+ skip_network=True)
def _backend_can_start_eip(self):
"""
@@ -683,10 +720,12 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnEIPPreferences.clicked
self.ui.action_eip_preferences (disabled for now)
- Displays the EIP preferences window.
+ Display the EIP preferences window.
"""
domain = self._login_widget.get_selected_provider()
- EIPPreferencesWindow(self, domain, self._backend).show()
+ pref = EIPPreferencesWindow(self, domain,
+ self._backend, self._leap_signaler)
+ pref.show()
#
# updates
@@ -707,7 +746,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.new_updates
- Displays the new updates label and sets the updates_content
+ Display the new updates label and sets the updates_content
:param req: Request type
:type req: leap.common.events.events_pb2.SignalRequest
@@ -821,7 +860,7 @@ class MainWindow(QtGui.QMainWindow):
"""
providers = self._settings.get_configured_providers()
- self._backend.provider_get_all_services(providers)
+ self._backend.provider_get_all_services(providers=providers)
def _provider_get_all_services(self, services):
self._set_eip_visible(EIP_SERVICE in services)
@@ -922,7 +961,7 @@ class MainWindow(QtGui.QMainWindow):
:param reason: the reason why the tray got activated.
:type reason: int
- Displays the context menu from the tray icon
+ Display the context menu from the tray icon
"""
self._update_hideshow_menu()
@@ -936,7 +975,7 @@ class MainWindow(QtGui.QMainWindow):
def _update_hideshow_menu(self):
"""
- Updates the Hide/Show main window menu text based on the
+ Update the Hide/Show main window menu text based on the
visibility of the window.
"""
get_action = lambda visible: (
@@ -953,7 +992,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._action_visible.triggered
- Toggles the window visibility
+ Toggle the window visibility
"""
visible = self.isVisible() and self.isActiveWindow()
@@ -981,7 +1020,7 @@ class MainWindow(QtGui.QMainWindow):
def _center_window(self):
"""
- Centers the mainwindow based on the desktop geometry
+ Center the main window based on the desktop geometry
"""
geometry = self._settings.get_geometry()
state = self._settings.get_windowstate()
@@ -1083,7 +1122,7 @@ class MainWindow(QtGui.QMainWindow):
def changeEvent(self, e):
"""
- Reimplements the changeEvent method to minimize to tray
+ Reimplementation of changeEvent method to minimize to tray
"""
if not IS_MAC and \
QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
@@ -1098,6 +1137,12 @@ class MainWindow(QtGui.QMainWindow):
"""
Reimplementation of closeEvent to close to tray
"""
+ if not e.spontaneous():
+ # if the system requested the `close` then we should quit.
+ self._system_quit = True
+ self.quit()
+ return
+
if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
not self._really_quit:
self._toggle_visible()
@@ -1111,7 +1156,7 @@ class MainWindow(QtGui.QMainWindow):
def _first_run(self):
"""
- Returns True if there are no configured providers. False otherwise
+ Return True if there are no configured providers. False otherwise
:rtype: bool
"""
@@ -1122,12 +1167,12 @@ class MainWindow(QtGui.QMainWindow):
def _download_provider_config(self):
"""
- Starts the bootstrapping sequence. It will download the
+ Start the bootstrapping sequence. It will download the
provider configuration if it's not present, otherwise will
emit the corresponding signals inmediately
"""
domain = self._login_widget.get_selected_provider()
- self._backend.provider_setup(domain)
+ self._backend.provider_setup(provider=domain)
@QtCore.Slot(dict)
def _load_provider_config(self, data):
@@ -1142,18 +1187,19 @@ class MainWindow(QtGui.QMainWindow):
backend.provider_setup()
:type data: dict
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
selected_provider = self._login_widget.get_selected_provider()
- self._backend.provider_bootstrap(selected_provider)
+ self._backend.provider_bootstrap(provider=selected_provider)
else:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
@QtCore.Slot()
def _login_problem_provider(self):
"""
- Warns the user about a problem with the provider during login.
+ Warn the user about a problem with the provider during login.
"""
+ # XXX triggers?
self._login_widget.set_status(
self.tr("Unable to login: Problem with provider"))
self._login_widget.set_enabled(True)
@@ -1164,7 +1210,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._login_widget.login
- Starts the login sequence. Which involves bootstrapping the
+ Start the login sequence. Which involves bootstrapping the
selected provider if the selection is valid (not empty), then
start the SRP authentication, and as the last step
bootstrapping the EIP service
@@ -1209,7 +1255,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._login_widget.cancel_login
- Stops the login sequence.
+ Stop the login sequence.
"""
logger.debug("Cancelling log in.")
self._cancel_ongoing_defers()
@@ -1233,7 +1279,7 @@ class MainWindow(QtGui.QMainWindow):
Signaler.prov_cancelled_setup fired by
self._backend.provider_cancel_setup()
- This method re-enables the login widget and display a message for
+ Re-enable the login widget and display a message for
the cancelled operation.
"""
self._login_widget.set_status(self.tr("Log in cancelled by the user."))
@@ -1248,16 +1294,17 @@ class MainWindow(QtGui.QMainWindow):
Once the provider configuration is loaded, this starts the SRP
authentication
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
username = self._login_widget.get_user()
password = self._login_widget.get_password()
self._show_hide_unsupported_services()
domain = self._login_widget.get_selected_provider()
- self._backend.user_login(domain, username, password)
+ self._backend.user_login(provider=domain,
+ username=username, password=password)
else:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
@QtCore.Slot()
@@ -1283,16 +1330,16 @@ class MainWindow(QtGui.QMainWindow):
if MX_SERVICE in self._enabled_services:
btn_enabled = self._login_widget.set_logout_btn_enabled
btn_enabled(False)
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True))
sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True))
- if not MX_SERVICE in self._provider_details['services']:
+ if MX_SERVICE not in self._provider_details['services']:
self._set_mx_visible(False)
def _start_eip_bootstrap(self):
"""
- Changes the stackedWidget index to the EIP status one and
+ Change the stackedWidget index to the EIP status one and
triggers the eip bootstrapping.
"""
@@ -1322,7 +1369,7 @@ class MainWindow(QtGui.QMainWindow):
"""
domain = self._login_widget.get_selected_provider()
lang = QtCore.QLocale.system().name()
- self._backend.provider_get_details(domain, lang)
+ self._backend.provider_get_details(domain=domain, lang=lang)
@QtCore.Slot()
def _provider_get_details(self, details):
@@ -1336,7 +1383,7 @@ class MainWindow(QtGui.QMainWindow):
def _provides_mx_and_enabled(self):
"""
- Defines if the current provider provides mx and if we have it enabled.
+ Define if the current provider provides mx and if we have it enabled.
:returns: True if provides and is enabled, False otherwise
:rtype: bool
@@ -1353,7 +1400,7 @@ class MainWindow(QtGui.QMainWindow):
def _provides_eip_and_enabled(self):
"""
- Defines if the current provider provides eip and if we have it enabled.
+ Define if the current provider provides eip and if we have it enabled.
:returns: True if provides and is enabled, False otherwise
:rtype: bool
@@ -1391,11 +1438,14 @@ class MainWindow(QtGui.QMainWindow):
# this is mostly for internal use/debug for now.
logger.warning("Sorry! Log-in at least one time.")
return
- self._backend.soledad_load_offline(full_user_id, password, uuid)
+ self._backend.soledad_load_offline(username=full_user_id,
+ password=password, uuid=uuid)
else:
if self._logged_user is not None:
domain = self._login_widget.get_selected_provider()
- self._backend.soledad_bootstrap(username, domain, password)
+ self._backend.soledad_bootstrap(username=username,
+ domain=domain,
+ password=password)
###################################################################
# Service control methods: soledad
@@ -1452,14 +1502,14 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _disable_eip_start_action(self):
"""
- Disables the EIP start action in the systray menu.
+ Disable the EIP start action in the systray menu.
"""
self._action_eip_startstop.setEnabled(False)
@QtCore.Slot()
def _enable_eip_start_action(self):
"""
- Enables the EIP start action in the systray menu.
+ Enable the EIP start action in the systray menu.
"""
self._action_eip_startstop.setEnabled(True)
self._eip_status.enable_eip_start()
@@ -1474,14 +1524,27 @@ class MainWindow(QtGui.QMainWindow):
signal that currently is beeing processed under status_panel.
After the refactor to EIPConductor this should not be necessary.
"""
- domain = self._login_widget.get_selected_provider()
+ self._already_started_eip = True
- self._eip_status.set_provider(domain)
+ domain = self._login_widget.get_selected_provider()
self._settings.set_defaultprovider(domain)
- self._already_started_eip = True
+
+ self._backend.eip_get_gateway_country_code(domain=domain)
# check for connectivity
- self._backend.eip_check_dns(domain)
+ self._backend.eip_check_dns(domain=domain)
+
+ @QtCore.Slot()
+ def _set_eip_provider(self, country_code=None):
+ """
+ TRIGGERS:
+ Signaler.eip_get_gateway_country_code
+ Signaler.eip_no_gateway
+
+ Set the current provider and country code in the eip status widget.
+ """
+ domain = self._login_widget.get_selected_provider()
+ self._eip_status.set_provider(domain, country_code)
@QtCore.Slot()
def _eip_dns_error(self):
@@ -1503,7 +1566,7 @@ class MainWindow(QtGui.QMainWindow):
def _try_autostart_eip(self):
"""
- Tries to autostart EIP
+ Try to autostart EIP.
"""
settings = self._settings
default_provider = settings.get_defaultprovider()
@@ -1523,6 +1586,8 @@ class MainWindow(QtGui.QMainWindow):
:param autostart: we are autostarting EIP when this is True
:type autostart: bool
"""
+ # XXX should move to EIP conductor.
+
# during autostart we assume that the provider provides EIP
if autostart:
should_start = EIP_SERVICE in self._enabled_services
@@ -1542,10 +1607,16 @@ class MainWindow(QtGui.QMainWindow):
self._enable_eip_start_action()
self._eip_status.set_eip_status(
self.tr("Starting..."))
+ self._eip_status.show_eip_cancel_button()
+
+ # We were disabling the button, but now that we have
+ # a cancel button we just hide it. It will be visible
+ # when the connection is completed successfully.
+ self._eip_status.eip_button.hide()
self._eip_status.eip_button.setEnabled(False)
domain = self._login_widget.get_selected_provider()
- self._backend.eip_setup(domain)
+ self._backend.eip_setup(provider=domain)
self._already_started_eip = True
# we want to start soledad anyway after a certain timeout if eip
@@ -1577,12 +1648,12 @@ class MainWindow(QtGui.QMainWindow):
Start the VPN thread if the eip configuration is properly
loaded.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
error_msg = self.tr("There was a problem with the provider")
self._eip_status.set_eip_status(error_msg, error=True)
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._already_started_eip = False
return
@@ -1600,11 +1671,11 @@ class MainWindow(QtGui.QMainWindow):
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
self._login_widget.set_status(
self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._already_started_eip = False
self._eip_status.aborted()
@@ -1616,7 +1687,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._login_widget.logout
- Starts the logout sequence
+ Start the logout sequence
"""
self._cancel_ongoing_defers()
@@ -1644,7 +1715,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGER:
self._srp_auth.logout_ok
- Switches the stackedWidget back to the login stage after
+ Switch the stackedWidget back to the login stage after
logging out
"""
self._login_widget.done_logout()
@@ -1665,13 +1736,13 @@ class MainWindow(QtGui.QMainWindow):
self._backend.signaler.prov_https_connection
self._backend.signaler.prov_download_ca_cert
- If there was a problem, displays it, otherwise it does nothing.
+ If there was a problem, display it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
#
@@ -1717,10 +1788,11 @@ class MainWindow(QtGui.QMainWindow):
self._services_being_stopped = set(('imap', 'eip'))
imap_stopped = lambda: self._remove_service('imap')
- self._backend.signaler.imap_stopped.connect(imap_stopped)
+ self._leap_signaler.imap_stopped.connect(imap_stopped)
+ # XXX change name, already used in conductor.
eip_stopped = lambda: self._remove_service('eip')
- self._backend.signaler.eip_stopped.connect(eip_stopped)
+ self._leap_signaler.eip_stopped.connect(eip_stopped)
logger.debug('Stopping mail services')
self._backend.imap_stop_service()
@@ -1738,12 +1810,16 @@ class MainWindow(QtGui.QMainWindow):
Start the quit sequence and wait for services to finish.
Cleanup and close the main window before quitting.
"""
- # TODO separate the shutting down of services from the
- # UI stuff.
+ if self._quitting:
+ return
+
+ autostart.set_autostart(False)
+
+ self._quitting = True
# first thing to do quitting, hide the mainwindow and show tooltip.
self.hide()
- if self._systray is not None:
+ if not self._system_quit and self._systray is not None:
self._systray.showMessage(
self.tr('Quitting...'),
self.tr('Bitmask is quitting, please wait.'))
@@ -1755,21 +1831,39 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard:
self._wizard.close()
- if self._logger_window:
- self._logger_window.close()
-
# Set this in case that the app is hidden
QtGui.QApplication.setQuitOnLastWindowClosed(True)
- self._stop_services()
-
self._really_quit = True
+ if not self._backend.online:
+ self.final_quit()
+ return
+
# call final quit when all the services are stopped
self.all_services_stopped.connect(self.final_quit)
+
+ self._stop_services()
+
+ # we wait and call manually since during the system's logout the
+ # backend process can be killed and we won't get a response.
+ # XXX: also, for some reason the services stop timeout does not work.
+ if self._system_quit:
+ time.sleep(0.5)
+ self.final_quit()
+
# or if we reach the timeout
QtDelayedCall(self.SERVICES_STOP_TIMEOUT, self.final_quit)
+ def _backend_kill(self):
+ """
+ Send a kill signal to the backend process.
+ This is called if the backend does not respond to requests.
+ """
+ if self._backend_pid is not None:
+ logger.debug("Killing backend")
+ psutil.Process(self._backend_pid).kill()
+
@QtCore.Slot()
def _remove_service(self, service):
"""
@@ -1780,6 +1874,7 @@ class MainWindow(QtGui.QMainWindow):
:param service: the service that we want to remove
:type service: str
"""
+ logger.debug("Removing service: {0}".format(service))
self._services_being_stopped.discard(service)
if not self._services_being_stopped:
@@ -1792,22 +1887,31 @@ class MainWindow(QtGui.QMainWindow):
Final steps to quit the app, starting from here we don't care about
running services or user interaction, just quitting.
"""
- logger.debug('Final quit...')
-
# We can reach here because all the services are stopped or because a
# timeout was triggered. Since we want to run this only once, we exit
# if this is called twice.
if self._finally_quitting:
return
+ logger.debug('Final quit...')
self._finally_quitting = True
+ if self._backend.online:
+ logger.debug('Closing soledad...')
+ self._backend.soledad_close()
+
+ self._leap_signaler.stop()
+
+ self._backend.stop()
+ time.sleep(0.05) # give the thread a little time to finish.
+
+ if self._system_quit or not self._backend.online:
+ logger.debug("Killing the backend")
+ self._backend_kill()
+
# Remove lockfiles on a clean shutdown.
logger.debug('Cleaning pidfiles')
if IS_WIN:
WindowsLock.release_all_locks()
- self._backend.stop()
self.close()
-
- QtDelayedCall(100, twisted_main.quit)
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index a3b81d38..3c9cd5d0 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -38,7 +38,8 @@ class PreferencesWindow(QtGui.QDialog):
"""
preferences_saved = QtCore.Signal()
- def __init__(self, parent, username, domain, backend, soledad_started, mx):
+ def __init__(self, parent, username, domain, backend, soledad_started, mx,
+ leap_signaler):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
@@ -58,6 +59,7 @@ class PreferencesWindow(QtGui.QDialog):
self._username = username
self._domain = domain
+ self._leap_signaler = leap_signaler
self._backend = backend
self._soledad_started = soledad_started
self._mx_provided = mx
@@ -194,7 +196,8 @@ class PreferencesWindow(QtGui.QDialog):
return
self._set_changing_password(True)
- self._backend.user_change_password(current_password, new_password)
+ self._backend.user_change_password(current_password=current_password,
+ new_password=new_password)
@QtCore.Slot()
def _srp_change_password_ok(self):
@@ -208,7 +211,7 @@ class PreferencesWindow(QtGui.QDialog):
logger.debug("SRP password changed successfully.")
if self._mx_provided:
- self._backend.soledad_change_password(new_password)
+ self._backend.soledad_change_password(new_password=new_password)
else:
self._change_password_success()
@@ -357,7 +360,7 @@ class PreferencesWindow(QtGui.QDialog):
save_services = partial(self._save_enabled_services, domain)
self.ui.pbSaveServices.clicked.connect(save_services)
- self._backend.provider_get_supported_services(domain)
+ self._backend.provider_get_supported_services(domain=domain)
@QtCore.Slot(str)
def _load_services(self, services):
@@ -423,7 +426,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
Helper to connect to backend signals
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.prov_get_supported_services.connect(self._load_services)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 00a1387e..91f1f605 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -240,9 +240,9 @@ class CompositeMachine(QStateMachine):
c2.qtsigs.disconnected_signal.connect(self.off_ev2_slot)
# XXX why is this getting deletec in c++?
- #Traceback (most recent call last):
- #self.postEvent(self.events.on_ev2)
- #RuntimeError: Internal C++ object (ConnectedEvent2) already deleted.
+ # Traceback (most recent call last):
+ # self.postEvent(self.events.on_ev2)
+ # RuntimeError: Internal C++ object (ConnectedEvent2) already deleted.
# XXX trying the following workaround, since
# I cannot find why in the world this is getting deleted :(
# XXX refactor!
@@ -318,9 +318,8 @@ class ConnectionMachineBuilder(object):
components = self._conn.components
if components is None:
- # simple case: connection definition inherits directly from
- # the abstract connection.
-
+ # simple case: connection definition inherits directly from
+ # the abstract connection.
leap_assert_type(self._conn, connections.AbstractLEAPConnection)
return self._make_simple_machine(self._conn, **kwargs)
diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py
deleted file mode 100644
index b1ce0ead..00000000
--- a/src/leap/bitmask/gui/twisted_main.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-# twisted_main.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/>.
-"""
-Main functions for integration of twisted reactor
-"""
-import logging
-
-from twisted.internet import error, reactor
-from PySide import QtCore
-
-logger = logging.getLogger(__name__)
-
-
-def stop():
- QtCore.QCoreApplication.sendPostedEvents()
- QtCore.QCoreApplication.flush()
- try:
- reactor.stop()
- logger.debug('Twisted reactor stopped')
- except error.ReactorNotRunning:
- logger.debug('Twisted reactor not running')
- logger.debug("Done stopping all the things.")
-
-
-def quit():
- """
- Stop the mainloop.
- """
- reactor.callLater(0, stop)
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index 7216bb0a..e0996620 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -28,13 +28,26 @@
<property name="verticalSpacing">
<number>0</number>
</property>
- <item row="0" column="4">
+ <item row="0" column="5">
<widget class="QPushButton" name="btnEipStartStop">
<property name="text">
<string>Turn On</string>
</property>
</widget>
</item>
+ <item row="1" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="maximumSize">
@@ -86,7 +99,7 @@
</property>
</widget>
</item>
- <item row="0" column="5">
+ <item row="0" column="6">
<widget class="QLabel" name="lblVPNStatusIcon">
<property name="maximumSize">
<size>
@@ -105,20 +118,7 @@
</property>
</widget>
</item>
- <item row="1" column="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="2" colspan="4">
+ <item row="2" column="2" colspan="5">
<widget class="QWidget" name="eip_bandwidth" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
@@ -239,7 +239,7 @@
</layout>
</widget>
</item>
- <item row="0" column="3">
+ <item row="0" column="4">
<widget class="QPushButton" name="btnFwDown">
<property name="text">
<string>Turn Off</string>
@@ -253,6 +253,13 @@
</property>
</widget>
</item>
+ <item row="0" column="3">
+ <widget class="QPushButton" name="btnEipCancel">
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index d755115a..1eeb636f 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>722</height>
+ <height>640</height>
</rect>
</property>
<property name="sizePolicy">
@@ -75,7 +75,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>667</height>
+ <height>589</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -297,7 +297,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>23</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index f66c553d..be5bde52 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -24,6 +24,10 @@ from functools import partial
from PySide import QtCore, QtGui
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
+
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.services import get_service_display_name, get_supported
@@ -49,16 +53,12 @@ class Wizard(QtGui.QWizard):
REGISTER_USER_PAGE = 4
SERVICES_PAGE = 5
- def __init__(self, backend, bypass_checks=False):
+ def __init__(self, backend, leap_signaler):
"""
Constructor for the main Wizard.
:param backend: Backend being used
:type backend: Backend
- :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)
@@ -83,7 +83,10 @@ class Wizard(QtGui.QWizard):
self.ui.grpCheckProvider.setVisible(False)
self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider)
- self._connect_and_track(self.ui.lnProvider.returnPressed, self._check_provider)
+ self._connect_and_track(self.ui.lnProvider.returnPressed,
+ self._check_provider)
+
+ self._leap_signaler = leap_signaler
self._backend = backend
self._backend_connect()
@@ -98,22 +101,24 @@ class Wizard(QtGui.QWizard):
self._provider_select_defer = None
self._provider_setup_defer = None
- self._connect_and_track(self.currentIdChanged, self._current_id_changed)
+ self._connect_and_track(self.currentIdChanged,
+ self._current_id_changed)
- self._connect_and_track(self.ui.lnProvider.textChanged, self._enable_check)
+ self._connect_and_track(self.ui.lnProvider.textChanged,
+ self._enable_check)
self._connect_and_track(self.ui.rbNewProvider.toggled,
- lambda x: self._enable_check())
+ lambda x: self._enable_check())
self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],
- self._reset_provider_check)
+ self._reset_provider_check)
self._connect_and_track(self.ui.lblUser.returnPressed,
- self._focus_password)
+ self._focus_password)
self._connect_and_track(self.ui.lblPassword.returnPressed,
- self._focus_second_password)
+ self._focus_second_password)
self._connect_and_track(self.ui.lblPassword2.returnPressed,
- self._register)
+ self._register)
self._connect_and_track(self.ui.btnRegister.clicked,
- self._register)
+ self._register)
self._connect_and_track(self.ui.rbExistingProvider.toggled,
self._skip_provider_checks)
@@ -184,21 +189,55 @@ class Wizard(QtGui.QWizard):
:param pinned: list of pinned providers
:type pinned: list of str
+
+
+ How the combobox items are arranged:
+ -----------------------------------
+
+ First run:
+
+ demo.bitmask.net
+ --
+ pinned2.org
+ pinned1.org
+ pinned3.org
+
+ After some usage:
+
+ added-by-user.org
+ pinned-but-then-used.org
+ ---
+ demo.bitmask.net
+ pinned1.org
+ pinned3.org
+ pinned2.org
+
+ In other words:
+ * There are two sections.
+ * Section one consists of all the providers that the user has used.
+ If this is empty, than use demo.bitmask.net for this section.
+ This list is sorted alphabetically.
+ * Section two consists of all the pinned or 'pre seeded' providers,
+ minus any providers that are now in section one. This last list
+ is in random order.
"""
ls = LeapSettings()
- providers = ls.get_configured_providers()
- if not providers and not pinned:
+ user_added = ls.get_configured_providers()
+ if not user_added and not pinned:
self.ui.rbExistingProvider.setEnabled(False)
self.ui.label_8.setEnabled(False) # 'https://' label
self.ui.cbProviders.setEnabled(False)
return
- user_added = []
+ user_added.sort()
+
+ if not user_added:
+ user_added = [pinned.pop(0)]
- # separate pinned providers from user added ones
- for p in providers:
- if p not in pinned:
- user_added.append(p)
+ # separate unused pinned providers from user added ones
+ for p in user_added:
+ if p in pinned:
+ pinned.remove(p)
if user_added:
self.ui.cbProviders.addItems(user_added)
@@ -288,7 +327,8 @@ class Wizard(QtGui.QWizard):
if user_ok and pass_ok:
self._set_register_status(self.tr("Starting registration..."))
- self._backend.user_register(self._domain, username, password)
+ self._backend.user_register(provider=self._domain,
+ username=username, password=password)
self._username = username
self._password = password
else:
@@ -440,7 +480,7 @@ class Wizard(QtGui.QWizard):
self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
self._provider_select_defer = self._backend.\
- provider_setup(self._domain)
+ provider_setup(provider=self._domain)
@QtCore.Slot(bool)
def _skip_provider_checks(self, skip):
@@ -475,8 +515,8 @@ class Wizard(QtGui.QWizard):
:param complete_page: page id to complete
:type complete_page: int
"""
- passed = data[self._backend.PASSED_KEY]
- error = data[self._backend.ERROR_KEY]
+ passed = data[PASSED_KEY]
+ error = data[ERROR_KEY]
if passed:
label.setPixmap(self.OK_ICON)
if complete:
@@ -496,7 +536,7 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblNameResolution)
status = ""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>Non-existent "
"provider</b></font>")
@@ -516,10 +556,10 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblHTTPS)
status = ""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>%s</b></font>") \
- % (data[self._backend.ERROR_KEY])
+ % (data[ERROR_KEY])
self.ui.lblProviderSelectStatus.setText(status)
else:
self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
@@ -536,22 +576,21 @@ class Wizard(QtGui.QWizard):
check. Since this check is the last of this set, it also
completes the page if passed
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
self._complete_task(data, self.ui.lblProviderInfo,
True, self.SELECT_PROVIDER_PAGE)
self._provider_checks_ok = True
lang = QtCore.QLocale.system().name()
- self._backend.provider_get_details(self._domain, lang)
+ self._backend.provider_get_details(domain=self._domain, lang=lang)
else:
new_data = {
- self._backend.PASSED_KEY: False,
- self._backend.ERROR_KEY:
- self.tr("Unable to load provider configuration")
+ PASSED_KEY: False,
+ ERROR_KEY: self.tr("Unable to load provider configuration")
}
self._complete_task(new_data, self.ui.lblProviderInfo)
status = ""
- if not data[self._backend.PASSED_KEY]:
+ if not data[PASSED_KEY]:
status = self.tr("<font color='red'><b>Not a valid provider"
"</b></font>")
self.ui.lblProviderSelectStatus.setText(status)
@@ -582,7 +621,7 @@ class Wizard(QtGui.QWizard):
Sets the status for the download of the CA certificate check
"""
self._complete_task(data, self.ui.lblDownloadCaCert)
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if passed:
self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
@@ -595,7 +634,7 @@ class Wizard(QtGui.QWizard):
Sets the status for the CA fingerprint check
"""
self._complete_task(data, self.ui.lblCheckCaFpr)
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if passed:
self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
@@ -689,7 +728,7 @@ class Wizard(QtGui.QWizard):
self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
self._provider_setup_defer = self._backend.\
- provider_bootstrap(self._domain)
+ provider_bootstrap(provider=self._domain)
if pageId == self.PRESENT_PROVIDER_PAGE:
sub_title = self.tr("Description of services offered by {0}")
@@ -752,7 +791,7 @@ class Wizard(QtGui.QWizard):
"""
Connects all the backend signals with the wizard.
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
conntrack = self._connect_and_track
conntrack(sig.prov_name_resolution, self._name_resolution)
conntrack(sig.prov_https_connection, self._https_connection)
diff --git a/src/leap/bitmask/platform_init/__init__.py b/src/leap/bitmask/platform_init/__init__.py
index 2a262a30..a9a97af9 100644
--- a/src/leap/bitmask/platform_init/__init__.py
+++ b/src/leap/bitmask/platform_init/__init__.py
@@ -22,7 +22,7 @@ import platform
_system = platform.system()
-IS_WIN = True if _system == "Windows" else False
-IS_MAC = True if _system == "Darwin" else False
-IS_LINUX = True if _system == "Linux" else False
+IS_LINUX = _system == "Linux"
+IS_MAC = _system == "Darwin"
IS_UNIX = IS_MAC or IS_LINUX
+IS_WIN = _system == "Windows"
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index 2d800703..e4d6f9b3 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -21,7 +21,6 @@ import logging
import os
import platform
import stat
-import sys
import subprocess
import tempfile
@@ -103,7 +102,7 @@ def get_missing_helpers_dialog():
msg.setWindowTitle(msg.tr("Missing helper files"))
msg.setText(msg.tr(WE_NEED_POWERS))
# but maybe the user really deserve to know more
- #msg.setInformativeText(msg.tr(BECAUSE))
+ # msg.setInformativeText(msg.tr(BECAUSE))
msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
msg.setDefaultButton(QtGui.QMessageBox.Yes)
diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py
index 38851621..6fd2fa70 100644
--- a/src/leap/bitmask/provider/pinned.py
+++ b/src/leap/bitmask/provider/pinned.py
@@ -32,6 +32,7 @@ class PinnedProviders(object):
CONFIG_KEY = "config"
CACERT_KEY = "cacert"
+ PREFERRED_PROVIDER = pinned_demobitmask.DOMAIN
PROVIDERS = {
pinned_demobitmask.DOMAIN: {
@@ -50,11 +51,16 @@ class PinnedProviders(object):
@classmethod
def domains(self):
"""
- Return the domains that are pinned in here
+ Return the domains that are pinned in here.
+ The first domain in the list is the preferred one.
:rtype: list of str
"""
- return self.PROVIDERS.keys()
+ domains = self.PROVIDERS.keys()
+ domains.remove(self.PREFERRED_PROVIDER)
+ domains.insert(0, self.PREFERRED_PROVIDER)
+
+ return domains
@classmethod
def save_hardcoded(self, domain, provider_path, cacert_path):
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 8c96a8b5..71edbb87 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -90,7 +90,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._provider_config = None
self._download_if_needed = False
if signaler is not None:
- self._cancel_signal = signaler.PROV_CANCELLED_SETUP
+ self._cancel_signal = signaler.prov_cancelled_setup
@property
def verify(self):
@@ -194,8 +194,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
verify = self.verify
if mtime: # the provider.json exists
- # So, we're getting it from the api.* and checking against
- # the provider ca.
+ # So, we're getting it from the api.* and checking against
+ # the provider ca.
try:
provider_config = ProviderConfig()
provider_config.load(provider_json)
@@ -228,7 +228,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
# TODO split
if not provider.supports_client(min_client_version):
self._signaler.signal(
- self._signaler.PROV_UNSUPPORTED_CLIENT)
+ self._signaler.prov_unsupported_client)
raise UnsupportedClientVersionError()
provider_definition, mtime = get_content(res)
@@ -250,7 +250,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
'Found: {1}.').format(api_supported, api_version)
logger.error(error)
- self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API)
+ self._signaler.signal(self._signaler.prov_unsupported_api)
raise UnsupportedProviderAPI(error)
def run_provider_select_checks(self, domain, download_if_needed=False):
@@ -271,10 +271,10 @@ class ProviderBootstrapper(AbstractBootstrapper):
cb_chain = [
(self._check_name_resolution,
- self._signaler.PROV_NAME_RESOLUTION_KEY),
- (self._check_https, self._signaler.PROV_HTTPS_CONNECTION_KEY),
+ self._signaler.prov_name_resolution),
+ (self._check_https, self._signaler.prov_https_connection),
(self._download_provider_info,
- self._signaler.PROV_DOWNLOAD_PROVIDER_INFO_KEY)
+ self._signaler.prov_download_provider_info)
]
return self.addCallbackChain(cb_chain)
@@ -401,11 +401,11 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._download_ca_cert, self._signaler.PROV_DOWNLOAD_CA_CERT_KEY),
+ (self._download_ca_cert, self._signaler.prov_download_ca_cert),
(self._check_ca_fingerprint,
- self._signaler.PROV_CHECK_CA_FINGERPRINT_KEY),
+ self._signaler.prov_check_ca_fingerprint),
(self._check_api_certificate,
- self._signaler.PROV_CHECK_API_CERTIFICATE_KEY)
+ self._signaler.prov_check_api_certificate)
]
return self.addCallbackChain(cb_chain)
diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py
index a8821160..0ee56628 100644
--- a/src/leap/bitmask/services/eip/conductor.py
+++ b/src/leap/bitmask/services/eip/conductor.py
@@ -16,6 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
EIP Conductor module.
+
+This handles Qt Signals and triggers the calls to the backend,
+where the VPNProcess has been initialized.
"""
import logging
@@ -33,7 +36,7 @@ logger = logging.getLogger(__name__)
class EIPConductor(object):
- def __init__(self, settings, backend, **kwargs):
+ def __init__(self, settings, backend, leap_signaler, **kwargs):
"""
Initializes EIP Conductor.
@@ -46,6 +49,7 @@ class EIPConductor(object):
self.eip_connection = EIPConnection()
self.eip_name = get_service_display_name(EIP_SERVICE)
self._settings = settings
+ self._leap_signaler = leap_signaler
self._backend = backend
self._eip_status = None
@@ -76,7 +80,7 @@ class EIPConductor(object):
"""
Connect to backend signals.
"""
- signaler = self._backend.signaler
+ signaler = self._leap_signaler
# for conductor
signaler.eip_process_restart_tls.connect(self._do_eip_restart)
@@ -89,7 +93,7 @@ class EIPConductor(object):
def start_eip_machine(self, action):
"""
- Initializes and starts the EIP state machine.
+ Initialize and start the EIP state machine.
Needs the reference to the eip_status widget not to be empty.
:action: QtAction
@@ -123,8 +127,12 @@ class EIPConductor(object):
@QtCore.Slot()
def _start_eip(self):
"""
- Starts EIP.
+ Start EIP.
+
+ This set a couple of status flags and calls the start procedure in the
+ backend.
"""
+ # TODO status should be kept in a singleton in the backend.
st = self._eip_status
is_restart = st and st.is_restart
@@ -136,6 +144,7 @@ class EIPConductor(object):
else:
self._eip_status.eip_pre_up()
self.user_stopped_eip = False
+ self.cancelled = False
self._eip_status.hide_fw_down_button()
# Until we set an option in the preferences window, we'll assume that
@@ -174,6 +183,9 @@ class EIPConductor(object):
:param failed: whether this is the final step of a retry sequence
:type failed: bool
"""
+ # XXX we should NOT keep status in the widget, but we do for a series
+ # of hacks related to restarts. All status should be kept in a backend
+ # object, widgets should be just widgets.
self._eip_status.is_restart = restart
self.user_stopped_eip = not restart and not failed
@@ -201,7 +213,7 @@ class EIPConductor(object):
# we bypass the on_eip_disconnected here
plug_restart_on_disconnected()
self.qtsigs.disconnected_signal.emit()
- #QtDelayedCall(0, self.qtsigs.disconnected_signal.emit)
+ # QtDelayedCall(0, self.qtsigs.disconnected_signal.emit)
# ...and reconnect the original signal again, after having used the
# diversion
QtDelayedCall(500, reconnect_disconnected_signal)
@@ -266,7 +278,7 @@ class EIPConductor(object):
TRIGGERS:
Signaler.eip_process_finished
- Triggered when the EIP/VPN process finishes to set the UI
+ Triggered when the EIP/VPN process finishes, in order to set the UI
accordingly.
Ideally we would have the right exit code here,
@@ -300,19 +312,27 @@ class EIPConductor(object):
# XXX FIXME --- check exitcode is != 0 really.
# bitmask-root is masking the exitcode, so we might need
# to fix it on that side.
- #if exitCode != 0 and not self.user_stopped_eip:
- if not self.user_stopped_eip:
+ # if exitCode != 0 and not self.user_stopped_eip:
+
+ if not self.user_stopped_eip and not self.cancelled:
+ error = True
eip_status_label = self._eip_status.tr(
"{0} finished in an unexpected manner!")
eip_status_label = eip_status_label.format(self.eip_name)
- self._eip_status.eip_stopped()
self._eip_status.set_eip_status_icon("error")
self._eip_status.set_eip_status(eip_status_label,
- error=True)
+ error=error)
+ self._eip_status.eip_stopped()
signal = self.qtsigs.connection_died_signal
self._eip_status.show_fw_down_button()
self._eip_status.eip_failed_to_connect()
+ if self.cancelled:
+ signal = self.qtsigs.connection_aborted_signal
+ self._eip_status.set_eip_status_icon("error")
+ self._eip_status.eip_stopped()
+ self._eip_status.set_eip_status("", error=False)
+
if exitCode == 0 and IS_MAC:
# XXX remove this warning after I fix cocoasudo.
logger.warning("The above exit code MIGHT BE WRONG.")
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index 41d75052..f83e0170 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -46,7 +46,9 @@ class DarwinVPNLauncher(VPNLauncher):
INSTALL_MSG = ("\"Bitmask needs administrative privileges to install "
"missing scripts and fix permissions.\"")
- INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../")
+ # Hardcode the installation path for OSX for security, openvpn is
+ # run as root
+ INSTALL_PATH = "/Applications/Bitmask.app/"
INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../")
OPENVPN_BIN = 'openvpn.leap'
OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py
index c77977ce..264eac2e 100644
--- a/src/leap/bitmask/services/eip/eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/eipbootstrapper.py
@@ -53,7 +53,7 @@ class EIPBootstrapper(AbstractBootstrapper):
self._eip_config = None
self._download_if_needed = False
if signaler is not None:
- self._cancel_signal = signaler.EIP_CANCELLED_SETUP
+ self._cancel_signal = signaler.eip_cancelled_setup
def _download_config(self, *args):
"""
@@ -116,9 +116,9 @@ class EIPBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._download_config, self._signaler.EIP_CONFIG_READY),
+ (self._download_config, self._signaler.eip_config_ready),
(self._download_client_certificates,
- self._signaler.EIP_CLIENT_CERTIFICATE_READY)
+ self._signaler.eip_client_certificate_ready)
]
return self.addCallbackChain(cb_chain)
diff --git a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
index 6640a860..1888f2c9 100644
--- a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
@@ -30,7 +30,7 @@ import time
try:
import unittest2 as unittest
except ImportError:
- import unittest
+ import unittest # noqa - skip 'unused import' warning
from nose.twistedtools import deferred, reactor
from twisted.internet import threads
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index 0731bee3..72e19413 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -27,7 +27,7 @@ from abc import ABCMeta, abstractmethod
from functools import partial
from leap.bitmask.config import flags
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
@@ -122,12 +122,12 @@ class VPNLauncher(object):
:rtype: list
"""
gateways = []
- leap_settings = LeapSettings()
+ settings = Settings()
domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
+ gateway_conf = settings.get_selected_gateway(domain)
gateway_selector = VPNGatewaySelector(eipconfig)
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ if gateway_conf == GATEWAY_AUTOMATIC:
gateways = gateway_selector.get_gateways()
else:
gateways = [gateway_conf]
@@ -136,12 +136,6 @@ class VPNLauncher(object):
logger.error('No gateway was found!')
raise VPNLauncherException('No gateway was found!')
- # this only works for selecting the first gateway, as we're
- # currently doing.
- ccodes = gateway_selector.get_gateways_country_code()
- gateway_ccode = ccodes[gateways[0]]
- flags.CURRENT_VPN_COUNTRY = gateway_ccode
-
logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
return gateways
@@ -175,11 +169,11 @@ class VPNLauncher(object):
leap_assert_type(providerconfig, ProviderConfig)
# XXX this still has to be changed on osx and windows accordingly
- #kwargs = {}
- #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
- #if not openvpn_possibilities:
- #raise OpenVPNNotFoundException()
- #openvpn = first(openvpn_possibilities)
+ # kwargs = {}
+ # openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
+ # if not openvpn_possibilities:
+ # raise OpenVPNNotFoundException()
+ # openvpn = first(openvpn_possibilities)
# -----------------------------------------
openvpn_path = force_eval(kls.OPENVPN_BIN_PATH)
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index b54f2925..c7159a93 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -118,10 +118,10 @@ class VPNObserver(object):
"""
sig = self._signaler
signals = {
- "network_unreachable": sig.EIP_NETWORK_UNREACHABLE,
- "process_restart_tls": sig.EIP_PROCESS_RESTART_TLS,
- "process_restart_ping": sig.EIP_PROCESS_RESTART_PING,
- "initialization_completed": sig.EIP_CONNECTED
+ "network_unreachable": sig.eip_network_unreachable,
+ "process_restart_tls": sig.eip_process_restart_tls,
+ "process_restart_ping": sig.eip_process_restart_ping,
+ "initialization_completed": sig.eip_connected
}
return signals.get(event.lower())
@@ -202,7 +202,24 @@ class VPN(object):
"aborting openvpn launch.")
return
- cmd = vpnproc.getCommand()
+ # FIXME it would be good to document where the
+ # errors here are catched, since we currently handle them
+ # at the frontend layer. This *should* move to be handled entirely
+ # in the backend.
+ # exception is indeed technically catched in backend, then converted
+ # into a signal, that is catched in the eip_status widget in the
+ # frontend, and converted into a signal to abort the connection that is
+ # sent to the backend again.
+
+ # the whole exception catching should be done in the backend, without
+ # the ping-pong to the frontend, and without adding any logical checks
+ # in the frontend. We should just communicate UI changes to frontend,
+ # and abstract us away from anything else.
+ try:
+ cmd = vpnproc.getCommand()
+ except Exception:
+ logger.error("Error while getting vpn command...")
+ raise
env = os.environ
for key, val in vpnproc.vpn_env.items():
env[key] = val
@@ -255,11 +272,26 @@ class VPN(object):
"""
Tear the firewall down using the privileged wrapper.
"""
+ if IS_MAC:
+ # We don't support Mac so far
+ return True
BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
exitCode = subprocess.call(["pkexec",
BM_ROOT, "firewall", "stop"])
return True if exitCode is 0 else False
+ def bitmask_root_vpn_down(self):
+ """
+ Bring openvpn down using the privileged wrapper.
+ """
+ if IS_MAC:
+ # We don't support Mac so far
+ return True
+ BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "openvpn", "stop"])
+ return True if exitCode is 0 else False
+
def _kill_if_left_alive(self, tries=0):
"""
Check if the process is still alive, and send a
@@ -594,7 +626,7 @@ class VPNManager(object):
state = status_step
if state != self._last_state:
- self._signaler.signal(self._signaler.EIP_STATE_CHANGED, state)
+ self._signaler.signal(self._signaler.eip_state_changed, state)
self._last_state = state
def _parse_status_and_notify(self, output):
@@ -632,7 +664,7 @@ class VPNManager(object):
status = (tun_tap_read, tun_tap_write)
if status != self._last_status:
- self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status)
+ self._signaler.signal(self._signaler.eip_status_changed, status)
self._last_status = status
def get_state(self):
@@ -814,7 +846,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
leap_assert_type(eipconfig, EIPConfig)
leap_assert_type(providerconfig, ProviderConfig)
- #leap_assert(not self.isRunning(), "Starting process more than once!")
+ # leap_assert(not self.isRunning(), "Starting process more than once!")
self._eipconfig = eipconfig
self._providerconfig = providerconfig
@@ -869,7 +901,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
if isinstance(exit_code, int):
logger.debug("processExited, status %d" % (exit_code,))
self._signaler.signal(
- self._signaler.EIP_PROCESS_FINISHED, exit_code)
+ self._signaler.eip_process_finished, exit_code)
self._alive = False
def processEnded(self, reason):
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index 98b40929..5e85368f 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -64,7 +64,8 @@ class IMAPControl(object):
"""
Start imap service.
"""
- self._backend.imap_start_service(self.userid, flags.OFFLINE)
+ self._backend.imap_start_service(full_user_id=self.userid,
+ offline=flags.OFFLINE)
def stop_imap_service(self):
"""
@@ -146,7 +147,8 @@ class SMTPControl(object):
:type download_if_needed: bool
"""
self.smtp_connection.qtsigs.connecting_signal.emit()
- self._backend.smtp_start_service(self.userid, download_if_needed)
+ self._backend.smtp_start_service(full_user_id=self.userid,
+ download_if_needed=download_if_needed)
def stop_smtp_service(self):
"""
diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py
index c16a1fed..1af65c5d 100644
--- a/src/leap/bitmask/services/mail/plumber.py
+++ b/src/leap/bitmask/services/mail/plumber.py
@@ -26,7 +26,7 @@ from functools import partial
from twisted.internet import defer
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.provider import get_provider_path
from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths
@@ -83,7 +83,8 @@ def initialize_soledad(uuid, email, passwd,
secrets,
localdb,
server_url,
- cert_file)
+ cert_file,
+ defer_encryption=True)
return soledad
@@ -113,7 +114,7 @@ class MBOXPlumber(object):
self.user = user
self.mdir = mdir
self.sol = None
- self._settings = LeapSettings()
+ self._settings = Settings()
provider_config_path = os.path.join(get_path_prefix(),
get_provider_path(provider))
@@ -231,8 +232,8 @@ class MBOXPlumber(object):
with open(mail_filename) as f:
mail_string = f.read()
- #uid = self._mbox.getUIDNext()
- #print "saving with UID: %s" % uid
+ # uid = self._mbox.getUIDNext()
+ # print "saving with UID: %s" % uid
d = self._mbox.messages.add_msg(
mail_string, notify_on_disk=True)
return d
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index db12fd80..c4e43bfe 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -21,6 +21,7 @@ import logging
import os
import socket
import sys
+import time
from ssl import SSLError
from sqlite3 import ProgrammingError as sqlite_ProgrammingError
@@ -132,12 +133,15 @@ class SoledadBootstrapper(AbstractBootstrapper):
MAX_INIT_RETRIES = 10
MAX_SYNC_RETRIES = 10
+ WAIT_MAX_SECONDS = 600
+ # WAIT_STEP_SECONDS = 1
+ WAIT_STEP_SECONDS = 5
def __init__(self, signaler=None):
AbstractBootstrapper.__init__(self, signaler)
if signaler is not None:
- self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP
+ self._cancel_signal = signaler.soledad_cancelled_bootstrap
self._provider_config = None
self._soledad_config = None
@@ -181,17 +185,16 @@ class SoledadBootstrapper(AbstractBootstrapper):
:param uuid: the user uuid
:type uuid: str or unicode
"""
- print "UUID ", uuid
self._address = username
self._password = password
self._uuid = uuid
try:
self.load_and_sync_soledad(uuid, offline=True)
- self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED)
+ self._signaler.signal(self._signaler.soledad_offline_finished)
except Exception as e:
# TODO: we should handle more specific exceptions in here
logger.exception(e)
- self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED)
+ self._signaler.signal(self._signaler.soledad_offline_failed)
def _get_soledad_local_params(self, uuid, offline=False):
"""
@@ -356,12 +359,20 @@ class SoledadBootstrapper(AbstractBootstrapper):
Do several retries to get an initial soledad sync.
"""
# and now, let's sync
- sync_tries = 1
- while sync_tries <= self.MAX_SYNC_RETRIES:
+ sync_tries = self.MAX_SYNC_RETRIES
+ step = self.WAIT_STEP_SECONDS
+ max_wait = self.WAIT_MAX_SECONDS
+ while sync_tries > 0:
+ wait = 0
try:
logger.debug("Trying to sync soledad....")
self._try_soledad_sync()
- logger.debug("Soledad has been synced.")
+ while self.soledad.syncing:
+ time.sleep(step)
+ wait += step
+ if wait >= max_wait:
+ raise SoledadSyncError("timeout!")
+ logger.debug("Soledad has been synced!")
# so long, and thanks for all the fish
return
except SoledadSyncError:
@@ -379,9 +390,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
continue
except InvalidAuthTokenError:
self._signaler.signal(
- self._signaler.SOLEDAD_INVALID_AUTH_TOKEN)
+ self._signaler.soledad_invalid_auth_token)
raise
except Exception as e:
+ # XXX release syncing lock
logger.exception("Unhandled error while syncing "
"soledad: %r" % (e,))
break
@@ -423,7 +435,8 @@ class SoledadBootstrapper(AbstractBootstrapper):
local_db_path=local_db_path.encode(encoding),
server_url=server_url,
cert_file=cert_file.encode(encoding),
- auth_token=auth_token)
+ auth_token=auth_token,
+ defer_encryption=True)
# XXX All these errors should be handled by soledad itself,
# and return a subclass of SoledadInitializationFailed
@@ -448,7 +461,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
Raises SoledadSyncError if not successful.
"""
try:
- self._soledad.sync()
+ logger.debug("BOOTSTRAPPER: trying to sync Soledad....")
+ # pass defer_decryption=False to get inline decryption
+ # for debugging.
+ self._soledad.sync(defer_decryption=True)
except SSLError as exc:
logger.error("%r" % (exc,))
raise SoledadSyncError("Failed to sync soledad")
@@ -633,11 +649,11 @@ class SoledadBootstrapper(AbstractBootstrapper):
self._password = password
if flags.OFFLINE:
- signal_finished = self._signaler.SOLEDAD_OFFLINE_FINISHED
- signal_failed = self._signaler.SOLEDAD_OFFLINE_FAILED
+ signal_finished = self._signaler.soledad_offline_finished
+ signal_failed = self._signaler.soledad_offline_failed
else:
- signal_finished = self._signaler.SOLEDAD_BOOTSTRAP_FINISHED
- signal_failed = self._signaler.SOLEDAD_BOOTSTRAP_FAILED
+ signal_finished = self._signaler.soledad_bootstrap_finished
+ signal_failed = self._signaler.soledad_bootstrap_failed
try:
self._download_config()
diff --git a/src/leap/bitmask/services/tests/test_abstractbootstrapper.py b/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
index 3ac126ac..c3fda9e1 100644
--- a/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
+++ b/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
@@ -1,4 +1,4 @@
-## -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# test_abstrctbootstrapper.py
# Copyright (C) 2013 LEAP
#
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index 25b86874..caa94ec7 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -129,3 +129,28 @@ def force_eval(items):
return map(do_eval, items)
else:
return do_eval(items)
+
+
+def dict_to_flags(values):
+ """
+ Set the flags values given in the values dict.
+ If a value isn't provided then use the already existing one.
+
+ :param values: the values to set.
+ :type values: dict.
+ """
+ for k, v in values.items():
+ setattr(flags, k, v)
+
+
+def flags_to_dict():
+ """
+ Get the flags values in a dict.
+
+ :return: the values of flags into a dict.
+ :rtype: dict.
+ """
+ items = [i for i in dir(flags) if i[0] != '_']
+ values = {i: getattr(flags, i) for i in items}
+
+ return values
diff --git a/src/leap/bitmask/util/autostart.py b/src/leap/bitmask/util/autostart.py
new file mode 100644
index 00000000..d7a8afb8
--- /dev/null
+++ b/src/leap/bitmask/util/autostart.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# autostart.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Helpers to enable/disable bitmask's autostart.
+"""
+import logging
+import os
+
+from leap.bitmask.config import flags
+from leap.bitmask.platform_init import IS_LINUX
+from leap.common.files import mkdir_p
+
+logger = logging.getLogger(__name__)
+
+
+DESKTOP_ENTRY = """\
+[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Type=Application
+Name=Bitmask
+Comment=Secure Communication
+Exec=bitmask --start-hidden
+Terminal=false
+Icon=bitmask
+"""
+
+DESKTOP_ENTRY_PATH = os.path.expanduser("~/.config/autostart/")
+DESKTOP_ENTRY_FILE = os.path.join(DESKTOP_ENTRY_PATH, 'bitmask.desktop')
+
+
+def set_autostart(enabled):
+ """
+ Set the autostart mode to enabled or disabled depending on the parameter.
+ If `enabled` is `True`, save the autostart file to its place. Otherwise,
+ remove that file.
+ Right now we support only Linux autostart.
+
+ :param enabled: whether the autostart should be enabled or not.
+ :type enabled: bool
+ """
+ # we don't do autostart for bundle or systems different than Linux
+ if flags.STANDALONE or not IS_LINUX:
+ return
+
+ if enabled:
+ mkdir_p(DESKTOP_ENTRY_PATH)
+ with open(DESKTOP_ENTRY_FILE, 'w') as f:
+ f.write(DESKTOP_ENTRY)
+ else:
+ try:
+ os.remove(DESKTOP_ENTRY_FILE)
+ except OSError: # if the file does not exist
+ pass
+ except Exception as e:
+ logger.error("Problem disabling autostart, {0!r}".format(e))
diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py
index 0512d988..c580f19e 100644
--- a/src/leap/bitmask/util/keyring_helpers.py
+++ b/src/leap/bitmask/util/keyring_helpers.py
@@ -54,11 +54,8 @@ def _get_keyring_with_fallback():
return None
kr = keyring.get_keyring()
if not canuse(kr):
- try:
- kr_klass = keyring.backends.SecretService
- kr = kr_klass.Keyring()
- except AttributeError:
- logger.warning("Keyring cannot find SecretService Backend")
+ logger.debug("No usable keyring found")
+ return None
logger.debug("Selected keyring: %s" % (kr.__class__,))
if not canuse(kr):
logger.debug("Not using default keyring since it is obsolete")
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index c7fed0a3..cbd6d8a5 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -107,19 +107,19 @@ def build_parser():
'against domains.')
# Not in use, we might want to reintroduce them.
- #parser.add_argument('-i', '--no-provider-checks',
- #action="store_true", default=False,
- #help="skips download of provider config files. gets "
- #"config from local files only. Will fail if cannot "
- #"find any")
- #parser.add_argument('-k', '--no-ca-verify',
- #action="store_true", default=False,
- #help="(insecure). Skips verification of the server "
- #"certificate used in TLS handshake.")
- #parser.add_argument('-c', '--config', metavar="CONFIG FILE", nargs='?',
- #action="store", dest="config_file",
- #type=argparse.FileType('r'),
- #help='optional config file')
+ # parser.add_argument('-i', '--no-provider-checks',
+ # action="store_true", default=False,
+ # help="skips download of provider config files. gets "
+ # "config from local files only. Will fail if cannot "
+ # "find any")
+ # parser.add_argument('-k', '--no-ca-verify',
+ # action="store_true", default=False,
+ # help="(insecure). Skips verification of the server "
+ # "certificate used in TLS handshake.")
+ # parser.add_argument('-c', '--config', metavar="CONFIG FILE", nargs='?',
+ # action="store", dest="config_file",
+ # type=argparse.FileType('r'),
+ # help='optional config file')
return parser
diff --git a/src/leap/bitmask/util/polkit_agent.py b/src/leap/bitmask/util/polkit_agent.py
index 6fda2f88..7764f571 100644
--- a/src/leap/bitmask/util/polkit_agent.py
+++ b/src/leap/bitmask/util/polkit_agent.py
@@ -39,9 +39,9 @@ def _launch_agent():
logger.error('Exception while running polkit authentication agent '
'%s' % (exc,))
# XXX fix KDE launch. See: #3755
- #try:
- #subprocess.call(KDE_PATH)
- #except Exception as exc:
+ # try:
+ # subprocess.call(KDE_PATH)
+ # except Exception as exc:
def launch():
diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py
index adc3503f..f894d73b 100644
--- a/src/leap/bitmask/util/privilege_policies.py
+++ b/src/leap/bitmask/util/privilege_policies.py
@@ -87,8 +87,8 @@ class LinuxPolicyChecker(PolicyChecker):
else self.LINUX_POLKIT_FILE)
def is_missing_policy_permissions(self):
- # FIXME this name is quite confusing, it does not have anything to do with
- # file permissions.
+ # FIXME this name is quite confusing, it does not have anything to do
+ # with file permissions.
"""
Returns True if we could not find the appropriate policykit file
in place