diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b429595b790479ad70d36ccb323d8217c80c460b..4faceb98c75f1ec652543eb1133d2e5605dbd763 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,40 @@ History
 2014
 ====
 
+0.5.2 June 6 -- the "are we there yet" release:
++++++++++++++++++++++++++++++++++++++++++++++++
+
+- Unblock local multicast IPs from linux firewall, to allow SSDP and
+  Bonjour/mDNS to work.
+- Add support for gnome-shell polkit agent. Closes #4144, #4218.
+- Update username regex to support the same as webapp. Closes #5965.
+- Wrong error message for username too short. Fixes #5697.
+- Cleanup and refactor username/password validators.
+- Fix EIP autostart failing. Closes #5721.
+- Block ipv6 traffic for the moment. Closes #5693
+- Fix bug with ipv6 blocking that caused block to not get removed from
+  firewall when Bitmask quit.
+- Bring firewall down when switching EIP off. Closes #5687
+- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly.
+- Allow usernames to end in a digit.
+- Improve signal handling in the mainwindow and wizard.
+- Enable UI when OpenVPN bin is not found, plus check before starting
+  EIP. Fixes #5619.
+- Properly set the userid for SMTP.
+- Update EIP UI if it fails to download the config.
+- Make use of cmdline in psutil backwards-compatible. Closes #5689
+- Add versioning support to bitmask-root.
+- Show flag of country for eip exit node, if available. Related #1232
+- Fix nameserver restoring. Closes #5692
+- Warn user if resolvconf cannot be found.
+- Refactor Keymanager to backend. Closes #5711.
+- Cleanup backend from hacks. Closes #5698.
+- Improve wait and quit process.
+- Move soledad password change to backend.
+- Move Mail logic to backend.
+- Separate imap/smtp logic from conductor.
+- Refactor SoledadBootstrapper to backend. Closes #5481.
+
 0.5.1 May 16 -- the "lil less leaky" release:
 +++++++++++++++++++++++++++++++++++++++++++++
 
diff --git a/Makefile b/Makefile
index 358af1260d050cb8208d64a7f28970a04480f72e..73437449b1a6a822283affa25f1d46725638cb6d 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ PROJFILE = data/bitmask.pro
 #UI files to compile
 UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui advanced_key_management.ui
 #Qt resource files to compile
-RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc
+RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc eipstatus.qrc
 
 #pyuic4 and pyrcc4 binaries
 PYUIC = pyside-uic
diff --git a/data/images/countries/nl.png b/data/images/countries/nl.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe44791e32b790949b0317ab3c258864b9024ebe
Binary files /dev/null and b/data/images/countries/nl.png differ
diff --git a/data/images/countries/tr.png b/data/images/countries/tr.png
new file mode 100644
index 0000000000000000000000000000000000000000..be32f77e9910c0896c1ee8e7ed4f0edf815a517e
Binary files /dev/null and b/data/images/countries/tr.png differ
diff --git a/data/images/countries/us.png b/data/images/countries/us.png
new file mode 100644
index 0000000000000000000000000000000000000000..10f451fe85c41c6c9a06d543a57114ae2f87ecc1
Binary files /dev/null and b/data/images/countries/us.png differ
diff --git a/data/resources/eipstatus.qrc b/data/resources/eipstatus.qrc
new file mode 100644
index 0000000000000000000000000000000000000000..5d0f292466b79ba602e357a0c704f0c48067c872
--- /dev/null
+++ b/data/resources/eipstatus.qrc
@@ -0,0 +1,7 @@
+<RCC>
+  <qresource prefix="/">
+    <file>../images/countries/nl.png</file>
+    <file>../images/countries/tr.png</file>
+    <file>../images/countries/us.png</file>
+  </qresource>
+</RCC>
diff --git a/docs/man/bitmask-root.1.rst b/docs/man/bitmask-root.1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c18cc4d6c1088140d390aa67026bda7af32591be
--- /dev/null
+++ b/docs/man/bitmask-root.1.rst
@@ -0,0 +1,61 @@
+============
+bitmask-root
+============
+
+------------------------------------------------------------------------
+privileged helper for bitmask, the encrypted internet access toolkit.
+------------------------------------------------------------------------
+
+:Author: LEAP Encryption Access Project https://leap.se
+:Date:   2014-06-05
+:Copyright: GPLv3+
+:Version: 0.5.2
+:Manual section: 1
+:Manual group: General Commands Manual
+
+SYNOPSIS
+========
+
+bitmask-root [openvpn | firewall | version] [start | stop | isup] [ARGS]
+
+DESCRIPTION
+===========
+
+*bitmask-root* is a privileged helper for bitmask.
+
+It is used to start or stop openvpn and the bitmask firewall. To operate, it
+needs to be executed with root privileges.
+
+
+OPTIONS
+=======
+
+openvpn
+--------
+
+**start** [ARGS]       Starts openvpn. All args are passed to openvpn, and
+                       filtered against a list of allowed args. If the next
+                       argument is `restart`, the firewall will not be teared
+                       down in the case of errors lauching openvpn.
+
+**stop**               Stops openvpn.
+
+
+firewall
+---------
+
+**start** [GATEWAYS]   Starts the firewall. GATEWAYS is a list of EIP
+                       gateways to allow in the firewall.
+
+**stop**               Stops the firewall.
+
+version
+--------
+
+**version**             Prints the `bitmask-root` version string.
+
+
+BUGS
+====
+
+Please report any bugs to https://leap.se/code
diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst
index ed4f7133a58879db10934cfbe801e6dc3fbbb1a9..6eae7ff5c7fd12bdae3b7ab45bfdd48235c18c27 100644
--- a/docs/man/bitmask.1.rst
+++ b/docs/man/bitmask.1.rst
@@ -7,9 +7,9 @@ graphical client to control LEAP, the encrypted internet access toolkit.
 ------------------------------------------------------------------------
 
 :Author: LEAP Encryption Access Project https://leap.se
-:Date:   2013-08-23
+:Date:   2014-06-05
 :Copyright: GPLv3+
-:Version: 0.3.1
+:Version: 0.5.2
 :Manual section: 1
 :Manual group: General Commands Manual
 
@@ -80,26 +80,20 @@ WARNING
 
 This software is still in its early phases of testing. So don't trust your life to it! 
 
-At the current time, Bitmask is not compatible with ``openresolv``, but it works with ``resolvconf``.
 
 FILES
 =====
 
-/etc/leap/resolv-update
------------------------
-Post up/down script passed to openvpn. It writes /etc/resolv.conf to avoid dns leaks, and restores the original resolv.conf on exit.
 
-/etc/leap/resolv-head
----------------------
-/etc/leap/resolv-tail
----------------------
+/usr/share/polkit-1/actions/se.leap.bitmask.policy
+-------------------------------------------------------
 
-Custom entries that will appear in the written resolv.conf
+PolicyKit policy file, used for granting access to bitmask-root without the need of entering a password each time.
 
-/usr/share/polkit-1/actions/net.openvpn.gui.leap.policy
--------------------------------------------------------
+/usr/sbin/bitmask-root
+------------------------
 
-PolicyKit policy file, used for granting access to openvpn without the need of entering a password each time.
+Helper to launch and stop openvpn and the bitmask firewall.
 
 ~/.config/leap/
 ---------------
diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki
index fc99fdf042247b9a7c1e647cfd3709d0003abf42..075591a71087c2cb2d02cd44ebd462b2c7ab86c4 100644
--- a/docs/release_checklist.wiki
+++ b/docs/release_checklist.wiki
@@ -1,5 +1,6 @@
 = Bitmask Release Checklist (*) =
   * [ ] Check that all tests are passing!
+  * [ ] Check that the version in bitmask_client/pkg/linux/bitmask-root is bumped if needed.
   * [ ] Tag everything
     * Should be done for the following packages, in order:
       * [ ] 1. leap.common
diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root
index 136fd6a4c2b89da1bdf9f46e0f626fabdeeb78e6..1929b51ba1a297e2071b9a261303fe71d8884bf5 100755
--- a/pkg/linux/bitmask-root
+++ b/pkg/linux/bitmask-root
@@ -22,14 +22,15 @@ It should only be called by the Bitmask application.
 
 USAGE:
   bitmask-root firewall stop
-  bitmask-root firewall start GATEWAY1 GATEWAY2 ...
+  bitmask-root firewall start [restart] GATEWAY1 GATEWAY2 ...
   bitmask-root openvpn stop
   bitmask-root openvpn start CONFIG1 CONFIG1 ...
 
 All actions return exit code 0 for success, non-zero otherwise.
 
 The `openvpn start` action is special: it calls exec on openvpn and replaces
-the current process.
+the current process. If the `restart` parameter is passed, the firewall will
+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
@@ -38,18 +39,19 @@ import os
 import re
 import signal
 import socket
+import syslog
 import subprocess
 import sys
 import time
 import traceback
 
-
 cmdcheck = subprocess.check_output
 
 ##
 ## CONSTANTS
 ##
 
+VERSION = "1"
 SCRIPT = "bitmask-root"
 NAMESERVER = "10.42.0.1"
 BITMASK_CHAIN = "bitmask"
@@ -129,6 +131,8 @@ if DEBUG:
     logger.setLevel(logging.DEBUG)
     logger.addHandler(ch)
 
+syslog.openlog(SCRIPT)
+
 ##
 ## UTILITY
 ##
@@ -413,6 +417,7 @@ def bail(msg=None, exception=None):
     """
     if msg is not None:
         print("%s: %s" % (SCRIPT, msg))
+        syslog.syslog(syslog.LOG_ERR, msg)
     if exception is not None:
         traceback.print_exc()
     exit(1)
@@ -566,7 +571,7 @@ class NameserverRestorer(Daemon):
     A daemon that will restore the previous nameservers.
     """
 
-    def run(self):
+    def run(self, *args):
         """
         Run when daemonized.
         """
@@ -614,7 +619,7 @@ def get_default_device():
     """
     routes = subprocess.check_output([IP, "route", "show"])
     match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M)
-    if match.groups():
+    if match and match.groups():
         return match.group(1)
     else:
         bail("Could not find default device")
@@ -629,7 +634,7 @@ def get_local_network_ipv4(device):
     """
     addresses = cmdcheck([IP, "-o", "address", "show", "dev", device])
     match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M)
-    if match.groups():
+    if match and match.groups():
         return match.group(1)
     else:
         return None
@@ -644,7 +649,7 @@ def get_local_network_ipv6(device):
     """
     addresses = cmdcheck([IP, "-o", "address", "show", "dev", device])
     match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M)
-    if match.groups():
+    if match and match.groups():
         return match.group(1)
     else:
         return None
@@ -653,6 +658,7 @@ def get_local_network_ipv6(device):
 def run_iptable_with_check(cmd, *args, **options):
     """
     Run an iptables command checking to see if it should:
+      for --append: run only if rule does not already exist.
       for --insert: run only if rule does not already exist.
       for --delete: run only if rule does exist.
     other commands are run normally.
@@ -662,6 +668,11 @@ def run_iptable_with_check(cmd, *args, **options):
         check_code = run(cmd, *check_args, exitcode=True)
         if check_code != 0:
             run(cmd, *args, **options)
+    elif "--append" in args:
+        check_args = [arg.replace("--append", "--check") for arg in args]
+        check_code = run(cmd, *check_args, exitcode=True)
+        if check_code != 0:
+            run(cmd, *args, **options)
     elif "--delete" in args:
         check_args = [arg.replace("--delete", "--check") for arg in args]
         check_code = run(cmd, *check_args, exitcode=True)
@@ -729,41 +740,74 @@ def firewall_start(args):
     local_network_ipv6 = get_local_network_ipv6(default_device)
     gateways = get_gateways(args)
 
-    # add custom chain "bitmask"
+    # add custom chain "bitmask" to front of OUTPUT chain
     if not ipv4_chain_exists(BITMASK_CHAIN):
         ip4tables("--new-chain", BITMASK_CHAIN)
     if not ipv6_chain_exists(BITMASK_CHAIN):
         ip6tables("--new-chain", BITMASK_CHAIN)
     iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN)
 
-    # reject everything
-    iptables("--insert", BITMASK_CHAIN, "-o", default_device,
-             "--jump", "REJECT")
+    # allow DNS over VPN
+    for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]:
+        ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp",
+                  "--dport", "53", "--destination", allowed_dns,
+                  "--jump", "ACCEPT")
 
-    # allow traffic to gateways
-    for gateway in gateways:
-        ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway,
-                  "-o", default_device, "--jump", "ACCEPT")
+    # block DNS requests to anyone but the service provider or localhost
+    # (when we actually route ipv6, we will need DNS rules for it too)
+    ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53",
+              "--jump", "REJECT")
 
     # allow traffic to IPs on local network
     if local_network_ipv4:
-        ip4tables("--insert", BITMASK_CHAIN,
+        ip4tables("--append", BITMASK_CHAIN,
                   "--destination", local_network_ipv4, "-o", default_device,
                   "--jump", "ACCEPT")
+        # allow multicast Simple Service Discovery Protocol
+        ip4tables("--append", BITMASK_CHAIN,
+                  "--protocol", "udp",
+                  "--destination", "239.255.255.250", "--dport", "1900",
+                  "-o", default_device, "--jump", "RETURN")
+        # allow multicast Bonjour/mDNS
+        ip4tables("--append", BITMASK_CHAIN,
+                  "--protocol", "udp",
+                  "--destination", "224.0.0.251", "--dport", "5353",
+                  "-o", default_device, "--jump", "RETURN")
     if local_network_ipv6:
-        ip6tables("--insert", BITMASK_CHAIN,
+        ip6tables("--append", BITMASK_CHAIN,
                   "--destination", local_network_ipv6, "-o", default_device,
                   "--jump", "ACCEPT")
+        # allow multicast Simple Service Discovery Protocol
+        ip6tables("--append", BITMASK_CHAIN,
+                  "--protocol", "udp",
+                  "--destination", "FF05::C", "--dport", "1900",
+                  "-o", default_device, "--jump", "RETURN")
+        # allow multicast Bonjour/mDNS
+        ip6tables("--append", BITMASK_CHAIN,
+                  "--protocol", "udp",
+                  "--destination", "FF02::FB", "--dport", "5353",
+                  "-o", default_device, "--jump", "RETURN")
+
+    # allow ipv4 traffic to gateways
+    for gateway in gateways:
+        ip4tables("--append", BITMASK_CHAIN, "--destination", gateway,
+                  "-o", default_device, "--jump", "ACCEPT")
 
-    # block DNS requests to anyone but the service provider or localhost
-    # when we actually route ipv6, we will need dns rules for it too
-    ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53",
-              "--jump", "REJECT")
+    # log rejected packets to syslog
+    if DEBUG:
+        iptables("--append", BITMASK_CHAIN, "-o", default_device,
+                 "--jump", "LOG", "--log-prefix", "iptables denied: ",
+                 "--log-level", "7")
 
-    for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]:
-        ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp",
-                  "--dport", "53", "--destination", allowed_dns,
-                  "--jump", "ACCEPT")
+    # for now, ensure all other ipv6 packets get rejected (regardless of
+    # device)
+    # (not sure why, but "-p any" doesn't work)
+    ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT")
+    ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT")
+
+    # reject all other ipv4 sent over the default device
+    ip4tables("--append", BITMASK_CHAIN, "-o",
+              default_device, "--jump", "REJECT")
 
 
 def firewall_stop():
@@ -784,10 +828,27 @@ def firewall_stop():
 
 
 def main():
-    if len(sys.argv) >= 3:
+    """
+    Entry point for cmdline execution.
+    """
+    # TODO use argparse instead.
+
+    if len(sys.argv) >= 2:
         command = "_".join(sys.argv[1:3])
         args = sys.argv[3:]
 
+        is_restart = False
+        if args and args[0] == "restart":
+            is_restart = True
+            args.remove('restart')
+
+        if command == "version":
+            print(VERSION)
+            exit(0)
+
+        if os.getuid() != 0:
+            bail("ERROR: must be run as root")
+
         if command == "openvpn_start":
             openvpn_start(args)
 
@@ -799,8 +860,9 @@ def main():
                 firewall_start(args)
                 nameserver_setter.start(NAMESERVER)
             except Exception as ex:
-                nameserver_restorer.start()
-                firewall_stop()
+                if not is_restart:
+                    nameserver_restorer.start()
+                    firewall_stop()
                 bail("ERROR: could not start firewall", ex)
 
         elif command == "firewall_stop":
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 70427e63421343eb5abfae0c6009600f4e3a2fbb..3d6b33a3077b7fd757fcacad23f89741c1cf197d 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -11,10 +11,7 @@ srp>=1.0.2
 pyopenssl
 python-dateutil
 
-# since gnupg requires exactly 1.2.1, this chokes if we
-# don't specify a version. Selecting something lesser than
-# 2.0 is equivalent to pick 1.2.1. See #5489
-psutil<2.0
+psutil
 
 ipaddr
 twisted
diff --git a/relnotes.txt b/relnotes.txt
index a658a7821eebd955101c45ffddc45f3c589ebfdb..e95e8c15ac470ab6ffee650081942b50f6909aa4 100644
--- a/relnotes.txt
+++ b/relnotes.txt
@@ -1,8 +1,8 @@
-ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.1
+ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.2
 
 The LEAP  team is  pleased to announce  the immediate  availability of
-version 0.5.1  of Bitmask,  the Internet Encryption  Toolkit, codename
-"lil less leaky".
+version 0.5.2  of Bitmask,  the Internet Encryption  Toolkit, codename
+"are we there yet".
 
 https://downloads.leap.se/client/
 
@@ -43,13 +43,9 @@ NOT trust your life to it.
 
 WHAT CAN THIS VERSION OF BITMASK DO FOR ME?
 
-Bitmask  0.5.1 improves  greatly  its mail  support  and stability  in
-general, among other various bug fixes. You can refer to the CHANGELOG
-for the meat.
-
-As always,  you can  connect to the  Encrypted Internet  Proxy service
-offered by a  provider of your choice, and enjoy  a encrypted internet
-connection that the spying eyes can only track back to your provider.
+Bitmask  0.5.2 improves  greatly  its Encrypted  internet support  and
+stability in general, among other various  bug fixes. 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
@@ -108,6 +104,6 @@ beyond any border.
 
 The LEAP team,
 
-May 16, 2014
+June 6, 2014
 Somewhere in the middle of the intertubes.
 EOF
diff --git a/setup.py b/setup.py
index de31be4bf45bc0e65c5fefe592b1e9c46e127891..3d12db641233d2856f0544b3041d9d2a36ba792c 100755
--- a/setup.py
+++ b/setup.py
@@ -203,9 +203,9 @@ if IS_LINUX:
     # globally. Or make specific install command. See #3805
     data_files = [
         ("share/polkit-1/actions",
-         ["pkg/linux/polkit/net.openvpn.gui.leap.policy"]),
-        ("etc/leap/",
-         ["pkg/linux/resolv-update"]),
+         ["pkg/linux/polkit/se.leap.bitmask.policy"]),
+        ("/usr/sbin",
+         ["pkg/linux/bitmask-root"]),
     ]
 
 
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index e413ab4c08c59abb200a937550c10a9141a212a3..e965604a32fbb9dabe86e1c8ec0abce0492ab8eb 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -39,7 +39,6 @@
 # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M
 # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M
 #                (thanks to: http://www.glassgiant.com/ascii/)
-import logging
 import signal
 import sys
 import os
@@ -50,10 +49,7 @@ from PySide import QtCore, QtGui
 
 from leap.bitmask import __version__ as VERSION
 from leap.bitmask.util import leap_argparse
-from leap.bitmask.util import log_silencer, LOG_FORMAT
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
-from leap.bitmask.util.streamtologger import StreamToLogger
-from leap.bitmask.platform_init import IS_WIN
+from leap.bitmask.logs.utils import get_logger
 from leap.bitmask.services.mail import plumber
 from leap.common.events import server as event_server
 from leap.mail import __version__ as MAIL_VERSION
@@ -76,6 +72,7 @@ def sigint_handler(*args, **kwargs):
     mainwindow = args[0]
     mainwindow.quit()
 
+
 def sigterm_handler(*args, **kwargs):
     """
     Signal handler for SIGTERM.
@@ -87,89 +84,6 @@ def sigterm_handler(*args, **kwargs):
     mainwindow = args[0]
     mainwindow.quit()
 
-def add_logger_handlers(debug=False, logfile=None, replace_stdout=True):
-    """
-    Create the logger and attach the handlers.
-
-    :param debug: the level of the messages that we should log
-    :type debug: bool
-    :param logfile: the file name of where we should to save the logs
-    :type logfile: str
-    :return: the new logger with the attached handlers.
-    :rtype: logging.Logger
-    """
-    # TODO: get severity from command line args
-    if debug:
-        level = logging.DEBUG
-    else:
-        level = logging.WARNING
-
-    # Create logger and formatter
-    logger = logging.getLogger(name='leap')
-    logger.setLevel(level)
-    formatter = logging.Formatter(LOG_FORMAT)
-
-    # Console handler
-    try:
-        import coloredlogs
-        console = coloredlogs.ColoredStreamHandler(level=level)
-    except ImportError:
-        console = logging.StreamHandler()
-        console.setLevel(level)
-        console.setFormatter(formatter)
-        using_coloredlog = False
-    else:
-        using_coloredlog = True
-
-    if using_coloredlog:
-        replace_stdout = False
-
-    silencer = log_silencer.SelectiveSilencerFilter()
-    console.addFilter(silencer)
-    logger.addHandler(console)
-    logger.debug('Console handler plugged!')
-
-    # LEAP custom handler
-    leap_handler = LeapLogHandler()
-    leap_handler.setLevel(level)
-    leap_handler.addFilter(silencer)
-    logger.addHandler(leap_handler)
-    logger.debug('Leap handler plugged!')
-
-    # File handler
-    if logfile is not None:
-        logger.debug('Setting logfile to %s ', logfile)
-        fileh = logging.FileHandler(logfile)
-        fileh.setLevel(logging.DEBUG)
-        fileh.setFormatter(formatter)
-        fileh.addFilter(silencer)
-        logger.addHandler(fileh)
-        logger.debug('File handler plugged!')
-
-    if replace_stdout:
-        replace_stdout_stderr_with_logging(logger)
-
-    return logger
-
-
-def replace_stdout_stderr_with_logging(logger):
-    """
-    Replace:
-        - the standard output
-        - the standard error
-        - the twisted log output
-    with a custom one that writes to the logger.
-    """
-    # Disabling this on windows since it breaks ALL THE THINGS
-    # The issue for this is #4149
-    if not IS_WIN:
-        sys.stdout = StreamToLogger(logger, logging.DEBUG)
-        sys.stderr = StreamToLogger(logger, logging.ERROR)
-
-        # Replace twisted's logger to use our custom output.
-        from twisted.python import log
-        log.startLogging(sys.stdout)
-
 
 def do_display_version(opts):
     """
@@ -212,6 +126,14 @@ def main():
     mail_logfile = opts.mail_log_file
     start_hidden = opts.start_hidden
 
+    replace_stdout = True
+    if opts.repair or opts.import_maildir:
+        # We don't want too much clutter on the comand mode
+        # this could be more generic with a Command class.
+        replace_stdout = False
+
+    logger = get_logger(debug, logfile, replace_stdout)
+
     #############################################################
     # Given how paths and bundling works, we need to delay the imports
     # of certain parts that depend on this path settings.
@@ -230,13 +152,6 @@ def main():
 
     BaseConfig.standalone = standalone
 
-    replace_stdout = True
-    if opts.repair or opts.import_maildir:
-        # We don't want too much clutter on the comand mode
-        # this could be more generic with a Command class.
-        replace_stdout = False
-    logger = add_logger_handlers(debug, logfile, replace_stdout)
-
     # ok, we got logging in place, we can satisfy mail plumbing requests
     # and show logs there. it normally will exit there if we got that path.
     do_mail_plumbing(opts)
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py
index a2df465d4cece81c08eb777d67d570e5f8d8bb7b..3c97c797b885803ad301a7d85806bb0248a4a545 100644
--- a/src/leap/bitmask/backend.py
+++ b/src/leap/bitmask/backend.py
@@ -17,13 +17,13 @@
 """
 Backend for everything
 """
-import commands
 import logging
 import os
 import time
 
 from functools import partial
 from Queue import Queue, Empty
+from threading import Condition
 
 from twisted.internet import reactor
 from twisted.internet import threads, defer
@@ -31,46 +31,40 @@ from twisted.internet.task import LoopingCall
 from twisted.python import log
 
 import zope.interface
+import zope.proxy
 
 from leap.bitmask.config.providerconfig import ProviderConfig
 from leap.bitmask.crypto.srpauth import SRPAuth
 from leap.bitmask.crypto.srpregister import SRPRegister
 from leap.bitmask.platform_init import IS_LINUX
-from leap.bitmask.provider import get_provider_path
 from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.services import get_supported
 from leap.bitmask.services.eip import eipconfig
 from leap.bitmask.services.eip import get_openvpn_management
 from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
 
 from leap.bitmask.services.eip import vpnlauncher, vpnprocess
 from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher
+from leap.bitmask.services.eip import get_vpn_launcher
 
-from leap.common import certs as leap_certs
-
-# Frontend side
-from PySide import QtCore
+from leap.bitmask.services.mail.imapcontroller import IMAPController
+from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
+from leap.bitmask.services.mail.smtpconfig import SMTPConfig
 
-logger = logging.getLogger(__name__)
+from leap.bitmask.services.soledad.soledadbootstrapper import \
+    SoledadBootstrapper
 
+from leap.common import certs as leap_certs
 
-def get_provider_config(config, domain):
-    """
-    Return the ProviderConfig object for the given domain.
-    If it is already loaded in `config`, then don't reload.
+from leap.keymanager import openpgp
+from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch
 
-    :param config: a ProviderConfig object
-    :type conig: ProviderConfig
-    :param domain: the domain which config is required.
-    :type domain: unicode
+from leap.soledad.client import NoStorageSecret, PassphraseTooShort
 
-    :returns: True if the config was loaded successfully, False otherwise.
-    :rtype: bool
-    """
-    # TODO: see ProviderConfig.get_provider_config
-    if (not config.loaded() or config.get_domain() != domain):
-        config.load(get_provider_path(domain))
+# Frontend side
+from PySide import QtCore
 
-    return config.loaded()
+logger = logging.getLogger(__name__)
 
 
 class ILEAPComponent(zope.interface.Interface):
@@ -86,13 +80,13 @@ class ILEAPService(ILEAPComponent):
     Interface that every Service needs to implement
     """
 
-    def start(self):
+    def start(self, *args, **kwargs):
         """
         Start the service.
         """
         pass
 
-    def stop(self):
+    def stop(self, *args, **kwargs):
         """
         Stops the service.
         """
@@ -156,6 +150,7 @@ class Provider(object):
         :type bypass_checks: bool
         """
         self.key = "provider"
+        self._signaler = signaler
         self._provider_bootstrapper = ProviderBootstrapper(signaler,
                                                            bypass_checks)
         self._download_provider_defer = None
@@ -197,11 +192,11 @@ class Provider(object):
         """
         d = None
 
-        config = self._provider_config
-        if get_provider_config(config, provider):
+        config = ProviderConfig.get_provider_config(provider)
+        self._provider_config = config
+        if config is not None:
             d = self._provider_bootstrapper.run_provider_setup_checks(
-                self._provider_config,
-                download_if_needed=True)
+                config, download_if_needed=True)
         else:
             if self._signaler is not None:
                 self._signaler.signal(
@@ -213,6 +208,73 @@ class Provider(object):
             d = defer.Deferred()
         return d
 
+    def _get_services(self, domain):
+        """
+        Returns a list of services provided by the given provider.
+
+        :param domain: the provider to get the services from.
+        :type domain: str
+
+        :rtype: list of str
+        """
+        services = []
+        provider_config = ProviderConfig.get_provider_config(domain)
+        if provider_config is not None:
+            services = provider_config.get_services()
+
+        return services
+
+    def get_supported_services(self, domain):
+        """
+        Signal a list of supported services provided by the given provider.
+
+        :param domain: the provider to get the services from.
+        :type domain: str
+
+        Signals:
+            prov_get_supported_services -> list of unicode
+        """
+        services = get_supported(self._get_services(domain))
+
+        self._signaler.signal(
+            self._signaler.PROV_GET_SUPPORTED_SERVICES, services)
+
+    def get_all_services(self, providers):
+        """
+        Signal a list of services provided by all the configured providers.
+
+        :param providers: the list of providers to get the services.
+        :type providers: list
+
+        Signals:
+            prov_get_all_services -> list of unicode
+        """
+        services_all = set()
+
+        for domain in providers:
+            services = self._get_services(domain)
+            services_all = services_all.union(set(services))
+
+        self._signaler.signal(
+            self._signaler.PROV_GET_ALL_SERVICES, services_all)
+
+    def get_details(self, domain, lang=None):
+        """
+        Signal a ProviderConfigLight object with the current ProviderConfig
+        settings.
+
+        :param domain: the domain name of the provider.
+        :type domain: str
+        :param lang: the language to use for localized strings.
+        :type lang: str
+
+        Signals:
+            prov_get_details -> ProviderConfigLight
+        """
+        self._signaler.signal(
+            self._signaler.PROV_GET_DETAILS,
+            self._provider_config.get_light_config(domain, lang))
+
 
 class Register(object):
     """
@@ -246,8 +308,9 @@ class Register(object):
         :returns: the defer for the operation running in a thread.
         :rtype: twisted.internet.defer.Deferred
         """
-        config = ProviderConfig()
-        if get_provider_config(config, domain):
+        config = ProviderConfig.get_provider_config(domain)
+        self._provider_config = config
+        if config is not None:
             srpregister = SRPRegister(signaler=self._signaler,
                                       provider_config=config)
             return threads.deferToThread(
@@ -294,8 +357,9 @@ class EIP(object):
         :returns: the defer for the operation running in a thread.
         :rtype: twisted.internet.defer.Deferred
         """
-        config = self._provider_config
-        if get_provider_config(config, domain):
+        config = ProviderConfig.get_provider_config(domain)
+        self._provider_config = config
+        if config is not None:
             if skip_network:
                 return defer.Deferred()
             eb = self._eip_bootstrapper
@@ -314,9 +378,12 @@ class EIP(object):
         if d is not None:
             d.cancel()
 
-    def _start_eip(self):
+    def _start_eip(self, restart=False):
         """
         Start EIP
+
+        :param restart: whether is is a restart.
+        :type restart: bool
         """
         provider_config = self._provider_config
         eip_config = eipconfig.EIPConfig()
@@ -325,6 +392,11 @@ class EIP(object):
         loaded = eipconfig.load_eipconfig_if_needed(
             provider_config, eip_config, domain)
 
+        if not self._can_start(domain):
+            if self._signaler is not None:
+                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)
@@ -335,9 +407,10 @@ class EIP(object):
         host, port = get_openvpn_management()
         self._vpn.start(eipconfig=eip_config,
                         providerconfig=provider_config,
-                        socket_host=host, socket_port=port)
+                        socket_host=host, socket_port=port,
+                        restart=restart)
 
-    def start(self):
+    def start(self, *args, **kwargs):
         """
         Start the service.
         """
@@ -350,7 +423,7 @@ class EIP(object):
             return
 
         try:
-            self._start_eip()
+            self._start_eip(*args, **kwargs)
         except vpnprocess.OpenVPNAlreadyRunning:
             signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING)
         except vpnprocess.AlienOpenVPNAlreadyRunning:
@@ -370,17 +443,22 @@ class EIP(object):
         except Exception as e:
             logger.error("Unexpected problem: {0!r}".format(e))
         else:
-            # TODO: are we connected here?
-            signaler.signal(signaler.EIP_CONNECTED)
+            logger.debug('EIP: no errors')
 
-    def stop(self, shutdown=False):
+    def _do_stop(self, shutdown=False, restart=False):
         """
-        Stop the service.
+        Stop the service. This is run in a thread to avoid blocking.
         """
-        self._vpn.terminate(shutdown)
+        self._vpn.terminate(shutdown, restart)
         if IS_LINUX:
             self._wait_for_firewall_down()
 
+    def stop(self, shutdown=False, restart=False):
+        """
+        Stop the service.
+        """
+        return threads.deferToThread(self._do_stop, shutdown, restart)
+
     def _wait_for_firewall_down(self):
         """
         Wait for the firewall to come down.
@@ -393,15 +471,16 @@ class EIP(object):
         MAX_FW_WAIT_RETRIES = 25
         FW_WAIT_STEP = 0.5
 
-        retry = 0
-
-        fw_up_cmd = "pkexec /usr/sbin/bitmask-root firewall isup"
-        fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256
+        retry = 1
 
-        while retry < MAX_FW_WAIT_RETRIES:
-            if fw_is_down():
+        while retry <= MAX_FW_WAIT_RETRIES:
+            if self._vpn.is_fw_down():
+                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)
                 time.sleep(FW_WAIT_STEP)
                 retry += 1
         logger.warning("After waiting, firewall is not down... "
@@ -459,6 +538,12 @@ class EIP(object):
             self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS,
                                   filtered_domains)
 
+    def tear_fw_down(self):
+        """
+        Tear the firewall down.
+        """
+        self._vpn.tear_down_firewall()
+
     def get_gateways_list(self, domain):
         """
         Signal a list of gateways for the given provider.
@@ -497,6 +582,46 @@ class EIP(object):
             self._signaler.signal(
                 self._signaler.EIP_GET_GATEWAYS_LIST, gateways)
 
+    def _can_start(self, domain):
+        """
+        Returns True if it has everything that is needed to run EIP,
+        False otherwise
+
+        :param domain: the domain for the provider to check
+        :type domain: str
+        """
+        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_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain))
+
+        launcher = get_vpn_launcher()
+        if not os.path.isfile(launcher.OPENVPN_BIN_PATH):
+            logger.error("Cannot start OpenVPN, binary not found")
+            return False
+
+        # check for other problems
+        if not eip_loaded or provider_config is None:
+            logger.error("Cannot load provider and eip config, cannot "
+                         "autostart")
+            return False
+
+        client_cert_path = eip_config.\
+            get_client_cert_path(provider_config, about_to_download=False)
+
+        if leap_certs.should_redownload(client_cert_path):
+            logger.error("The client should redownload the certificate,"
+                         " cannot autostart")
+            return False
+
+        if not os.path.isfile(client_cert_path):
+            logger.error("Can't find the certificate, cannot autostart")
+            return False
+
+        return True
+
     def can_start(self, domain):
         """
         Signal whether it has everything that is needed to run EIP or not
@@ -508,35 +633,363 @@ class EIP(object):
             eip_can_start
             eip_cannot_start
         """
-        try:
-            eip_config = eipconfig.EIPConfig()
-            provider_config = ProviderConfig.get_provider_config(domain)
+        if self._can_start(domain):
+            if self._signaler is not None:
+                self._signaler.signal(self._signaler.EIP_CAN_START)
+        else:
+            if self._signaler is not None:
+                self._signaler.signal(self._signaler.EIP_CANNOT_START)
 
-            api_version = provider_config.get_api_version()
-            eip_config.set_api_version(api_version)
-            eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain))
 
-            # check for other problems
-            if not eip_loaded or provider_config is None:
-                raise Exception("Cannot load provider and eip config, cannot "
-                                "autostart")
+class Soledad(object):
+    """
+    Interfaces with setup of Soledad.
+    """
+    zope.interface.implements(ILEAPComponent)
 
-            client_cert_path = eip_config.\
-                get_client_cert_path(provider_config, about_to_download=False)
+    def __init__(self, soledad_proxy, keymanager_proxy, signaler=None):
+        """
+        Constructor for the Soledad component.
 
-            if leap_certs.should_redownload(client_cert_path):
-                raise Exception("The client should redownload the certificate,"
-                                " cannot autostart")
+        :param soledad_proxy: proxy to pass around a Soledad object.
+        :type soledad_proxy: zope.ProxyBase
+        :param keymanager_proxy: proxy to pass around a Keymanager object.
+        :type keymanager_proxy: zope.ProxyBase
+        :param signaler: Object in charge of handling communication
+                         back to the frontend
+        :type signaler: Signaler
+        """
+        self.key = "soledad"
+        self._soledad_proxy = soledad_proxy
+        self._keymanager_proxy = keymanager_proxy
+        self._signaler = signaler
+        self._soledad_bootstrapper = SoledadBootstrapper(signaler)
+        self._soledad_defer = None
 
-            if not os.path.isfile(client_cert_path):
-                raise Exception("Can't find the certificate, cannot autostart")
+    def bootstrap(self, username, domain, password):
+        """
+        Bootstrap Soledad with the user credentials.
 
+        Signals:
+            soledad_download_config
+            soledad_gen_key
+
+        :param user: user's login
+        :type user: unicode
+        :param domain: the domain that we are using.
+        :type domain: unicode
+        :param password: user's password
+        :type password: unicode
+        """
+        provider_config = ProviderConfig.get_provider_config(domain)
+        if provider_config is not None:
+            self._soledad_defer = threads.deferToThread(
+                self._soledad_bootstrapper.run_soledad_setup_checks,
+                provider_config, username, password,
+                download_if_needed=True)
+            self._soledad_defer.addCallback(self._set_proxies_cb)
+        else:
             if self._signaler is not None:
-                self._signaler.signal(self._signaler.EIP_CAN_START)
-        except Exception as e:
-            logger.exception(e)
-            if self._signaler is not None:
-                self._signaler.signal(self._signaler.EIP_CANNOT_START)
+                self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED)
+            logger.error("Could not load provider configuration.")
+
+        return self._soledad_defer
+
+    def _set_proxies_cb(self, _):
+        """
+        Update the soledad and keymanager proxies to reference the ones created
+        in the bootstrapper.
+        """
+        zope.proxy.setProxiedObject(self._soledad_proxy,
+                                    self._soledad_bootstrapper.soledad)
+        zope.proxy.setProxiedObject(self._keymanager_proxy,
+                                    self._soledad_bootstrapper.keymanager)
+
+    def load_offline(self, username, password, uuid):
+        """
+        Load the soledad database in offline mode.
+
+        :param username: full user id (user@provider)
+        :type username: str or unicode
+        :param password: the soledad passphrase
+        :type password: unicode
+        :param uuid: the user uuid
+        :type uuid: str or unicode
+
+        Signals:
+            Signaler.soledad_offline_finished
+            Signaler.soledad_offline_failed
+        """
+        self._soledad_bootstrapper.load_offline_soledad(
+            username, password, uuid)
+
+    def cancel_bootstrap(self):
+        """
+        Cancel the ongoing soledad bootstrap (if any).
+        """
+        if self._soledad_defer is not None:
+            logger.debug("Cancelling soledad defer.")
+            self._soledad_defer.cancel()
+            self._soledad_defer = None
+            zope.proxy.setProxiedObject(self._soledad_proxy, None)
+
+    def close(self):
+        """
+        Close soledad database.
+        """
+        if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None):
+            self._soledad_proxy.close()
+            zope.proxy.setProxiedObject(self._soledad_proxy, None)
+
+    def _change_password_ok(self, _):
+        """
+        Password change callback.
+        """
+        if self._signaler is not None:
+            self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK)
+
+    def _change_password_error(self, failure):
+        """
+        Password change errback.
+
+        :param failure: failure object containing problem.
+        :type failure: twisted.python.failure.Failure
+        """
+        if failure.check(NoStorageSecret):
+            logger.error("No storage secret for password change in Soledad.")
+        if failure.check(PassphraseTooShort):
+            logger.error("Passphrase too short.")
+
+        if self._signaler is not None:
+            self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR)
+
+    def change_password(self, new_password):
+        """
+        Change the database's password.
+
+        :param new_password: the new password.
+        :type new_password: unicode
+
+        :returns: a defer to interact with.
+        :rtype: twisted.internet.defer.Deferred
+        """
+        d = threads.deferToThread(self._soledad_proxy.change_passphrase,
+                                  new_password)
+        d.addCallback(self._change_password_ok)
+        d.addErrback(self._change_password_error)
+
+
+class Keymanager(object):
+    """
+    Interfaces with KeyManager.
+    """
+    zope.interface.implements(ILEAPComponent)
+
+    def __init__(self, keymanager_proxy, signaler=None):
+        """
+        Constructor for the Keymanager component.
+
+        :param keymanager_proxy: proxy to pass around a Keymanager object.
+        :type keymanager_proxy: zope.ProxyBase
+        :param signaler: Object in charge of handling communication
+                         back to the frontend
+        :type signaler: Signaler
+        """
+        self.key = "keymanager"
+        self._keymanager_proxy = keymanager_proxy
+        self._signaler = signaler
+
+    def import_keys(self, username, filename):
+        """
+        Imports the username's key pair.
+        Those keys need to be ascii armored.
+
+        :param username: the user that will have the imported pair of keys.
+        :type username: str
+        :param filename: the name of the file where the key pair is stored.
+        :type filename: str
+        """
+        # NOTE: This feature is disabled right now since is dangerous
+        return
+
+        new_key = ''
+        signal = None
+        try:
+            with open(filename, 'r') as keys_file:
+                new_key = keys_file.read()
+        except IOError as e:
+            logger.error("IOError importing key. {0!r}".format(e))
+            signal = self._signaler.KEYMANAGER_IMPORT_IOERROR
+            self._signaler.signal(signal)
+            return
+
+        keymanager = self._keymanager_proxy
+        try:
+            public_key, private_key = keymanager.parse_openpgp_ascii_key(
+                new_key)
+        except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
+            logger.error(repr(e))
+            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
+            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
+            self._signaler.signal(signal)
+            return
+
+        keymanager.delete_key(self._key)
+        keymanager.delete_key(self._key_priv)
+        keymanager.put_key(public_key)
+        keymanager.put_key(private_key)
+        keymanager.send_key(openpgp.OpenPGPKey)
+
+        logger.debug('Import ok')
+        signal = self._signaler.KEYMANAGER_IMPORT_OK
+
+        self._signaler.signal(signal)
+
+    def export_keys(self, username, filename):
+        """
+        Export the given username's keys to a file.
+
+        :param username: the username whos keys we need to export.
+        :type username: str
+        :param filename: the name of the file where we want to save the keys.
+        :type filename: str
+        """
+        keymanager = self._keymanager_proxy
+
+        public_key = keymanager.get_key(username, openpgp.OpenPGPKey)
+        private_key = keymanager.get_key(username, openpgp.OpenPGPKey,
+                                         private=True)
+        try:
+            with open(filename, 'w') as keys_file:
+                keys_file.write(public_key.key_data)
+                keys_file.write(private_key.key_data)
+
+            logger.debug('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)
+
+    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)
+
+    def get_key_details(self, username):
+        """
+        List all the keys stored in the local DB.
+        """
+        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)
+
+
+class Mail(object):
+    """
+    Interfaces with setup and launch of Mail.
+    """
+    # We give each service some time to come to a halt before forcing quit
+    SERVICE_STOP_TIMEOUT = 20
+
+    zope.interface.implements(ILEAPComponent)
+
+    def __init__(self, soledad_proxy, keymanager_proxy, signaler=None):
+        """
+        Constructor for the Mail component.
+
+        :param soledad_proxy: proxy to pass around a Soledad object.
+        :type soledad_proxy: zope.ProxyBase
+        :param keymanager_proxy: proxy to pass around a Keymanager object.
+        :type keymanager_proxy: zope.ProxyBase
+        :param signaler: Object in charge of handling communication
+                         back to the frontend
+        :type signaler: Signaler
+        """
+        self.key = "mail"
+        self._signaler = signaler
+        self._soledad_proxy = soledad_proxy
+        self._keymanager_proxy = keymanager_proxy
+        self._imap_controller = IMAPController(self._soledad_proxy,
+                                               self._keymanager_proxy)
+        self._smtp_bootstrapper = SMTPBootstrapper()
+        self._smtp_config = SMTPConfig()
+
+    def start_smtp_service(self, full_user_id, download_if_needed=False):
+        """
+        Start the SMTP service.
+
+        :param full_user_id: user id, in the form "user@provider"
+        :type full_user_id: str
+        :param download_if_needed: True if it should check for mtime
+                                   for the file
+        :type download_if_needed: bool
+
+        :returns: a defer to interact with.
+        :rtype: twisted.internet.defer.Deferred
+        """
+        return threads.deferToThread(
+            self._smtp_bootstrapper.start_smtp_service,
+            self._keymanager_proxy, full_user_id, download_if_needed)
+
+    def start_imap_service(self, full_user_id, offline=False):
+        """
+        Start the IMAP service.
+
+        :param full_user_id: user id, in the form "user@provider"
+        :type full_user_id: str
+        :param offline: whether imap should start in offline mode or not.
+        :type offline: bool
+
+        :returns: a defer to interact with.
+        :rtype: twisted.internet.defer.Deferred
+        """
+        return threads.deferToThread(
+            self._imap_controller.start_imap_service,
+            full_user_id, offline)
+
+    def stop_smtp_service(self):
+        """
+        Stop the SMTP service.
+
+        :returns: a defer to interact with.
+        :rtype: twisted.internet.defer.Deferred
+        """
+        return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service)
+
+    def _stop_imap_service(self):
+        """
+        Stop imap and wait until the service is stopped to signal that is done.
+        """
+        cv = Condition()
+        cv.acquire()
+        threads.deferToThread(self._imap_controller.stop_imap_service, cv)
+        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)
+
+    def stop_imap_service(self):
+        """
+        Stop imap service (fetcher, factory and port).
+
+        :returns: a defer to interact with.
+        :rtype: twisted.internet.defer.Deferred
+        """
+        return threads.deferToThread(self._stop_imap_service)
 
 
 class Authenticate(object):
@@ -556,6 +1009,7 @@ class Authenticate(object):
         """
         self.key = "authenticate"
         self._signaler = signaler
+        self._login_defer = None
         self._srp_auth = SRPAuth(ProviderConfig(), self._signaler)
 
     def login(self, domain, username, password):
@@ -572,8 +1026,8 @@ class Authenticate(object):
         :returns: the defer for the operation running in a thread.
         :rtype: twisted.internet.defer.Deferred
         """
-        config = ProviderConfig()
-        if get_provider_config(config, domain):
+        config = ProviderConfig.get_provider_config(domain)
+        if config is not None:
             self._srp_auth = SRPAuth(config, self._signaler)
             self._login_defer = self._srp_auth.authenticate(username, password)
             return self._login_defer
@@ -670,6 +1124,10 @@ class Signaler(QtCore.QObject):
     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_cancelled_setup = QtCore.Signal(object)
 
     # Signals for SRPRegister
@@ -703,6 +1161,7 @@ class Signaler(QtCore.QObject):
     eip_disconnected = QtCore.Signal(object)
     eip_connection_died = QtCore.Signal(object)
     eip_connection_aborted = QtCore.Signal(object)
+    eip_stopped = QtCore.Signal(object)
 
     # EIP problems
     eip_no_polkit_agent_error = QtCore.Signal(object)
@@ -727,11 +1186,38 @@ class Signaler(QtCore.QObject):
     eip_state_changed = QtCore.Signal(dict)
     eip_status_changed = QtCore.Signal(dict)
     eip_process_finished = QtCore.Signal(int)
+    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)
+
+    keymanager_key_details = 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)
@@ -751,6 +1237,9 @@ class Signaler(QtCore.QObject):
     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"
 
     SRP_REGISTRATION_FINISHED = "srp_registration_finished"
     SRP_REGISTRATION_FAILED = "srp_registration_failed"
@@ -777,6 +1266,8 @@ class Signaler(QtCore.QObject):
     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"
@@ -797,10 +1288,35 @@ class Signaler(QtCore.QObject):
     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"
 
+    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):
@@ -821,6 +1337,9 @@ class Signaler(QtCore.QObject):
             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.SRP_REGISTRATION_FINISHED,
             self.SRP_REGISTRATION_FAILED,
@@ -834,6 +1353,8 @@ class Signaler(QtCore.QObject):
             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,
@@ -872,6 +1393,29 @@ class Signaler(QtCore.QObject):
             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,
         ]
 
@@ -926,11 +1470,24 @@ class Backend(object):
         # Signaler object to translate commands into Qt signals
         self._signaler = Signaler()
 
+        # 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(Provider(self._signaler, bypass_checks))
         self._register(Register(self._signaler))
         self._register(Authenticate(self._signaler))
         self._register(EIP(self._signaler))
+        self._register(Soledad(self._soledad_proxy,
+                               self._keymanager_proxy,
+                               self._signaler))
+        self._register(Keymanager(self._keymanager_proxy,
+                                  self._signaler))
+        self._register(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
@@ -952,7 +1509,7 @@ class Backend(object):
         """
         Starts the looping call
         """
-        log.msg("Starting worker...")
+        logger.debug("Starting worker...")
         self._lc.start(0.01)
 
     def stop(self):
@@ -965,14 +1522,17 @@ class Backend(object):
         """
         Delayed stopping of worker. Called from `stop`.
         """
-        log.msg("Stopping worker...")
+        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.")
 
     def _register(self, component):
         """
@@ -986,8 +1546,7 @@ class Backend(object):
         try:
             self._components[component.key] = component
         except Exception:
-            log.msg("There was a problem registering %s" % (component,))
-            log.err()
+            logger.error("There was a problem registering %s" % (component,))
 
     def _signal_back(self, _, signal):
         """
@@ -1015,19 +1574,19 @@ class Backend(object):
                 # 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, log.err, cmd[2])
-                d.addCallbacks(self._done_action, log.err,
+                    d.addCallbacks(self._signal_back, logger.error, cmd[2])
+                d.addCallbacks(self._done_action, logger.error,
                                callbackKeywords={"d": d})
-                d.addErrback(log.err)
+                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:
+        except Exception as e:
             # But we log the rest
-            log.err()
+            logger.exception("Unexpected exception: {0!r}".format(e))
 
     def _done_action(self, _, d):
         """
@@ -1044,7 +1603,7 @@ class Backend(object):
     # this in two processes, the methods bellow can be changed to
     # send_multipart and this backend class will be really simple.
 
-    def setup_provider(self, provider):
+    def provider_setup(self, provider):
         """
         Initiate the setup for a provider.
 
@@ -1060,7 +1619,7 @@ class Backend(object):
         """
         self._call_queue.put(("provider", "setup_provider", None, provider))
 
-    def cancel_setup_provider(self):
+    def provider_cancel_setup(self):
         """
         Cancel the ongoing setup provider (if any).
         """
@@ -1081,7 +1640,48 @@ class Backend(object):
         """
         self._call_queue.put(("provider", "bootstrap", None, provider))
 
-    def register_user(self, provider, username, password):
+    def provider_get_supported_services(self, domain):
+        """
+        Signal a list of supported services provided by the given provider.
+
+        :param domain: the provider to get the services from.
+        :type domain: str
+
+        Signals:
+            prov_get_supported_services -> list of unicode
+        """
+        self._call_queue.put(("provider", "get_supported_services", None,
+                              domain))
+
+    def provider_get_all_services(self, providers):
+        """
+        Signal a list of services provided by all the configured providers.
+
+        :param providers: the list of providers to get the services.
+        :type providers: list
+
+        Signals:
+            prov_get_all_services -> list of unicode
+        """
+        self._call_queue.put(("provider", "get_all_services", None,
+                              providers))
+
+    def provider_get_details(self, domain, lang):
+        """
+        Signal a ProviderConfigLight object with the current ProviderConfig
+        settings.
+
+        :param domain: the domain name of the provider.
+        :type domain: str
+        :param lang: the language to use for localized strings.
+        :type lang: str
+
+        Signals:
+            prov_get_details -> ProviderConfigLight
+        """
+        self._call_queue.put(("provider", "get_details", None, domain, lang))
+
+    def user_register(self, provider, username, password):
         """
         Register a user using the domain and password given as parameters.
 
@@ -1100,7 +1700,7 @@ class Backend(object):
         self._call_queue.put(("register", "register_user", None, provider,
                               username, password))
 
-    def setup_eip(self, provider, skip_network=False):
+    def eip_setup(self, provider, skip_network=False):
         """
         Initiate the setup for a provider
 
@@ -1118,13 +1718,13 @@ class Backend(object):
         self._call_queue.put(("eip", "setup_eip", None, provider,
                               skip_network))
 
-    def cancel_setup_eip(self):
+    def eip_cancel_setup(self):
         """
         Cancel the ongoing setup EIP (if any).
         """
         self._call_queue.put(("eip", "cancel_setup_eip", None))
 
-    def start_eip(self):
+    def eip_start(self, restart=False):
         """
         Start the EIP service.
 
@@ -1145,19 +1745,25 @@ class Backend(object):
             eip_state_changed -> str
             eip_status_changed -> tuple of str (download, upload)
             eip_vpn_launcher_exception
+
+        :param restart: whether is is a restart.
+        :type restart: bool
         """
-        self._call_queue.put(("eip", "start", None))
+        self._call_queue.put(("eip", "start", None, restart))
 
-    def stop_eip(self, shutdown=False):
+    def eip_stop(self, shutdown=False, restart=False, failed=False):
         """
         Stop the EIP service.
 
-        :param shutdown:
+        :param shutdown: whether this is the final shutdown.
         :type shutdown: bool
+
+        :param restart: whether this is part of a restart.
+        :type restart: bool
         """
-        self._call_queue.put(("eip", "stop", None, shutdown))
+        self._call_queue.put(("eip", "stop", None, shutdown, restart))
 
-    def terminate_eip(self):
+    def eip_terminate(self):
         """
         Terminate the EIP service, not necessarily in a nice way.
         """
@@ -1209,7 +1815,13 @@ class Backend(object):
         self._call_queue.put(("eip", "can_start",
                               None, domain))
 
-    def login(self, provider, username, password):
+    def tear_fw_down(self):
+        """
+        Signal the need to tear the fw down.
+        """
+        self._call_queue.put(("eip", "tear_fw_down", None))
+
+    def user_login(self, provider, username, password):
         """
         Execute the whole authentication process for a user
 
@@ -1231,7 +1843,7 @@ class Backend(object):
         self._call_queue.put(("authenticate", "login", None, provider,
                               username, password))
 
-    def logout(self):
+    def user_logout(self):
         """
         Log out the current session.
 
@@ -1242,13 +1854,13 @@ class Backend(object):
         """
         self._call_queue.put(("authenticate", "logout", None))
 
-    def cancel_login(self):
+    def user_cancel_login(self):
         """
         Cancel the ongoing login (if any).
         """
         self._call_queue.put(("authenticate", "cancel_login", None))
 
-    def change_password(self, current_password, new_password):
+    def user_change_password(self, current_password, new_password):
         """
         Change the user's password.
 
@@ -1266,7 +1878,23 @@ class Backend(object):
         self._call_queue.put(("authenticate", "change_password", None,
                               current_password, new_password))
 
-    def get_logged_in_status(self):
+    def soledad_change_password(self, new_password):
+        """
+        Change the database's password.
+
+        :param new_password: the new password for the user.
+        :type new_password: unicode
+
+        Signals:
+            srp_not_logged_in_error
+            srp_password_change_ok
+            srp_password_change_badpw
+            srp_password_change_error
+        """
+        self._call_queue.put(("soledad", "change_password", None,
+                              new_password))
+
+    def user_get_logged_in_status(self):
         """
         Signal if the user is currently logged in or not.
 
@@ -1276,10 +1904,126 @@ class Backend(object):
         """
         self._call_queue.put(("authenticate", "get_logged_in_status", None))
 
-    ###########################################################################
-    # XXX HACK: this section is meant to be a place to hold methods and
-    # variables needed in the meantime while we migrate all to the backend.
+    def soledad_bootstrap(self, username, domain, password):
+        """
+        Bootstrap the soledad database.
+
+        :param username: the user name
+        :type username: unicode
+        :param domain: the domain that we are using.
+        :type domain: unicode
+        :param password: the password for the username
+        :type password: unicode
+
+        Signals:
+            soledad_bootstrap_finished
+            soledad_bootstrap_failed
+            soledad_invalid_auth_token
+        """
+        self._call_queue.put(("soledad", "bootstrap", None,
+                              username, domain, password))
+
+    def soledad_load_offline(self, username, password, uuid):
+        """
+        Load the soledad database in offline mode.
+
+        :param username: full user id (user@provider)
+        :type username: str or unicode
+        :param password: the soledad passphrase
+        :type password: unicode
+        :param uuid: the user uuid
+        :type uuid: str or unicode
+
+        Signals:
+        """
+        self._call_queue.put(("soledad", "load_offline", None,
+                              username, password, uuid))
+
+    def soledad_cancel_bootstrap(self):
+        """
+        Cancel the ongoing soledad bootstrapping process (if any).
+        """
+        self._call_queue.put(("soledad", "cancel_bootstrap", None))
+
+    def soledad_close(self):
+        """
+        Close soledad database.
+        """
+        self._call_queue.put(("soledad", "close", None))
+
+    def keymanager_list_keys(self):
+        """
+        Signal a list of public keys locally stored.
+
+        Signals:
+            keymanager_keys_list -> list
+        """
+        self._call_queue.put(("keymanager", "list_keys", None))
+
+    def keymanager_export_keys(self, username, filename):
+        """
+        Export the given username's keys to a file.
+
+        :param username: the username whos keys we need to export.
+        :type username: str
+        :param filename: the name of the file where we want to save the keys.
+        :type filename: str
+
+        Signals:
+            keymanager_export_ok
+            keymanager_export_error
+        """
+        self._call_queue.put(("keymanager", "export_keys", None,
+                              username, filename))
 
-    def get_provider_config(self):
-        provider_config = self._components["provider"]._provider_config
-        return provider_config
+    def keymanager_get_key_details(self, username):
+        """
+        Signal the given username's key details.
+
+        :param username: the username whos keys we need to get details.
+        :type username: str
+
+        Signals:
+            keymanager_key_details
+        """
+        self._call_queue.put(("keymanager", "get_key_details", None, username))
+
+    def smtp_start_service(self, full_user_id, download_if_needed=False):
+        """
+        Start the SMTP service.
+
+        :param full_user_id: user id, in the form "user@provider"
+        :type full_user_id: str
+        :param download_if_needed: True if it should check for mtime
+                                   for the file
+        :type download_if_needed: bool
+        """
+        self._call_queue.put(("mail", "start_smtp_service", None,
+                              full_user_id, download_if_needed))
+
+    def imap_start_service(self, full_user_id, offline=False):
+        """
+        Start the IMAP service.
+
+        :param full_user_id: user id, in the form "user@provider"
+        :type full_user_id: str
+        :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))
+
+    def smtp_stop_service(self):
+        """
+        Stop the SMTP service.
+        """
+        self._call_queue.put(("mail", "stop_smtp_service", None))
+
+    def imap_stop_service(self):
+        """
+        Stop imap service.
+
+        Signals:
+            imap_stopped
+        """
+        self._call_queue.put(("mail", "stop_imap_service", None))
diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index 6b70659dd9ef66627197c16beeb944f4d380f1e6..2f3fdde4bf734a34e7f3dba2db3864bf1c06e619 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -55,3 +55,5 @@ 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/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index 2ebe05ce0cf95ec8d532cd1156d8e319f00d7867..cf31b3b26970d46f735b483a6729ec40ed5cdb2e 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -38,6 +38,35 @@ class MissingCACert(Exception):
     pass
 
 
+class ProviderConfigLight(object):
+    """
+    A light config object to hold some provider settings needed by the GUI.
+    """
+    def __init__(self):
+        """
+        Define the public attributes.
+        """
+        self.domain = ""
+        self.name = ""
+        self.description = ""
+        self.enrollment_policy = ""
+        self.services = []
+
+    @property
+    def services_string(self):
+        """
+        Return a comma separated list of serices provided by this provider.
+
+        :rtype: str
+        """
+        services = []
+        for service in self.services:
+            services.append(get_service_display_name(service))
+
+        services_str = ", ".join(services)
+        return services_str
+
+
 class ProviderConfig(BaseConfig):
     """
     Provider configuration abstraction class
@@ -45,6 +74,29 @@ class ProviderConfig(BaseConfig):
     def __init__(self):
         BaseConfig.__init__(self)
 
+    def get_light_config(self, domain, lang=None):
+        """
+        Return a ProviderConfigLight object with the data for the loaded
+        object.
+
+        :param domain: the domain name of the provider.
+        :type domain: str
+        :param lang: the language to use for localized strings.
+        :type lang: str
+
+        :rtype: ProviderConfigLight or None if the ProviderConfig isn't loaded.
+        """
+        config = self.get_provider_config(domain)
+        details = ProviderConfigLight()
+
+        details.domain = config.get_domain()
+        details.name = config.get_name(lang=lang)
+        details.description = config.get_description(lang=lang)
+        details.enrollment_policy = config.get_enrollment_policy()
+        details.services = config.get_services()
+
+        return details
+
     @classmethod
     def get_provider_config(self, domain):
         """
@@ -144,18 +196,6 @@ class ProviderConfig(BaseConfig):
         services = self._safe_get_value("services")
         return services
 
-    def get_services_string(self):
-        """
-        Returns a string with the available services in the current
-        provider, ready to be shown to the user.
-        """
-        services = []
-        for service in self.get_services():
-            services.append(get_service_display_name(service))
-
-        services_str = ", ".join(services)
-        return services_str
-
     def get_ca_cert_path(self, about_to_download=False):
         """
         Returns the path to the certificate for the current provider.
@@ -199,39 +239,3 @@ class ProviderConfig(BaseConfig):
         :rtype: bool
         """
         return "mx" in self.get_services()
-
-
-if __name__ == "__main__":
-    logger = logging.getLogger(name='leap')
-    logger.setLevel(logging.DEBUG)
-    console = logging.StreamHandler()
-    console.setLevel(logging.DEBUG)
-    formatter = logging.Formatter(
-        '%(asctime)s '
-        '- %(name)s - %(levelname)s - %(message)s')
-    console.setFormatter(formatter)
-    logger.addHandler(console)
-
-    provider = ProviderConfig()
-
-    try:
-        provider.get_api_version()
-    except Exception as e:
-        assert isinstance(e, AssertionError), "Expected an assert"
-        print "Safe value getting is working"
-
-    # standalone minitest
-    #if provider.load("provider_bad.json"):
-    if provider.load("leap/providers/bitmask.net/provider.json"):
-        print provider.get_api_version()
-        print provider.get_ca_cert_fingerprint()
-        print provider.get_ca_cert_uri()
-        print provider.get_default_language()
-        print provider.get_description()
-        print provider.get_description(lang="asd")
-        print provider.get_domain()
-        print provider.get_enrollment_policy()
-        print provider.get_languages()
-        print provider.get_name()
-        print provider.get_services()
-        print provider.get_services_string()
diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
index be6b441021cdebbeeaba188899473ee618bcc384..b3a4ed8e78479292c5b609aac21d03f883135dea 100644
--- a/src/leap/bitmask/gui/advanced_key_management.py
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -19,11 +19,8 @@ Advanced Key Management
 """
 import logging
 
-from PySide import QtGui
-from zope.proxy import sameProxiedObjects
+from PySide import QtCore, QtGui
 
-from leap.keymanager import openpgp
-from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch
 from leap.bitmask.services import get_service_display_name, MX_SERVICE
 from ui_advanced_key_management import Ui_AdvancedKeyManagement
 
@@ -34,7 +31,7 @@ class AdvancedKeyManagement(QtGui.QDialog):
     """
     Advanced Key Management
     """
-    def __init__(self, parent, has_mx, user, keymanager, soledad):
+    def __init__(self, parent, has_mx, user, backend, soledad_started):
         """
         :param parent: parent object of AdvancedKeyManagement.
         :parent type: QWidget
@@ -43,10 +40,10 @@ class AdvancedKeyManagement(QtGui.QDialog):
         :type has_mx: bool
         :param user: the current logged in user.
         :type user: unicode
-        :param keymanager: the existing keymanager instance
-        :type keymanager: KeyManager
-        :param soledad: a loaded instance of Soledad
-        :type soledad: Soledad
+        :param backend: Backend being used
+        :type backend: Backend
+        :param soledad_started: whether soledad has started or not
+        :type soledad_started: bool
         """
         QtGui.QDialog.__init__(self, parent)
 
@@ -56,7 +53,6 @@ class AdvancedKeyManagement(QtGui.QDialog):
         # XXX: Temporarily disable the key import.
         self.ui.pbImportKeys.setVisible(False)
 
-        # if Soledad is not started yet
         if not has_mx:
             msg = self.tr("The provider that you are using "
                           "does not support {0}.")
@@ -64,8 +60,7 @@ class AdvancedKeyManagement(QtGui.QDialog):
             self._disable_ui(msg)
             return
 
-        # if Soledad is not started yet
-        if sameProxiedObjects(soledad, None):
+        if not soledad_started:
             msg = self.tr("To use this, you need to enable/start {0}.")
             msg = msg.format(get_service_display_name(MX_SERVICE))
             self._disable_ui(msg)
@@ -78,17 +73,12 @@ class AdvancedKeyManagement(QtGui.QDialog):
         #         "existing e-mails.")
         #     self.ui.lblStatus.setText(msg)
 
-        self._keymanager = keymanager
-        self._soledad = soledad
-
-        self._key = keymanager.get_key(user, openpgp.OpenPGPKey)
-        self._key_priv = keymanager.get_key(
-            user, openpgp.OpenPGPKey, private=True)
+        self._user = user
+        self._backend = backend
+        self._backend_connect()
 
         # show current key information
         self.ui.leUser.setText(user)
-        self.ui.leKeyID.setText(self._key.key_id)
-        self.ui.leFingerprint.setText(self._key.fingerprint)
 
         # set up connections
         self.ui.pbImportKeys.clicked.connect(self._import_keys)
@@ -98,7 +88,15 @@ class AdvancedKeyManagement(QtGui.QDialog):
         self.ui.twPublicKeys.horizontalHeader().setResizeMode(
             0, QtGui.QHeaderView.Stretch)
 
-        self._list_keys()
+        self._backend.keymanager_get_key_details(user)
+        self._backend.keymanager_list_keys()
+
+    def _keymanager_key_details(self, details):
+        """
+        Set the current user's key details into the gui.
+        """
+        self.ui.leKeyID.setText(details[0])
+        self.ui.leFingerprint.setText(details[1])
 
     def _disable_ui(self, msg):
         """
@@ -117,53 +115,11 @@ class AdvancedKeyManagement(QtGui.QDialog):
         Imports the user's key pair.
         Those keys need to be ascii armored.
         """
-        fileName, filtr = QtGui.QFileDialog.getOpenFileName(
+        file_name, filtr = QtGui.QFileDialog.getOpenFileName(
             self, self.tr("Open keys file"),
             options=QtGui.QFileDialog.DontUseNativeDialog)
 
-        if fileName:
-            new_key = ''
-            try:
-                with open(fileName, 'r') as keys_file:
-                    new_key = keys_file.read()
-            except IOError as e:
-                logger.error("IOError importing key. {0!r}".format(e))
-                QtGui.QMessageBox.critical(
-                    self, self.tr("Input/Output error"),
-                    self.tr("There was an error accessing the file.\n"
-                            "Import canceled."))
-                return
-
-            keymanager = self._keymanager
-            try:
-                public_key, private_key = keymanager.parse_openpgp_ascii_key(
-                    new_key)
-            except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
-                logger.error(repr(e))
-                QtGui.QMessageBox.warning(
-                    self, self.tr("Data mismatch"),
-                    self.tr("The public and private key should have the "
-                            "same address and fingerprint.\n"
-                            "Import canceled."))
-                return
-
-            if public_key is None or private_key is None:
-                QtGui.QMessageBox.warning(
-                    self, self.tr("Missing key"),
-                    self.tr("You need to provide the public AND private "
-                            "key in the same file.\n"
-                            "Import canceled."))
-                return
-
-            if public_key.address != self._key.address:
-                logger.error("The key does not match the ID")
-                QtGui.QMessageBox.warning(
-                    self, self.tr("Address mismatch"),
-                    self.tr("The identity for the key needs to be the same "
-                            "as your user address.\n"
-                            "Import canceled."))
-                return
-
+        if file_name:
             question = self.tr("Are you sure that you want to replace "
                                "the current key pair whith the imported?")
             res = QtGui.QMessageBox.question(
@@ -171,61 +127,152 @@ class AdvancedKeyManagement(QtGui.QDialog):
                 QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                 QtGui.QMessageBox.No)  # default No
 
-            if res == QtGui.QMessageBox.No:
-                return
+            if res == QtGui.QMessageBox.Yes:
+                self._backend.keymanager_import_keys(self._user, file_name)
+        else:
+            logger.debug('Import canceled by the user.')
 
-            keymanager.delete_key(self._key)
-            keymanager.delete_key(self._key_priv)
-            keymanager.put_key(public_key)
-            keymanager.put_key(private_key)
-            keymanager.send_key(openpgp.OpenPGPKey)
+    @QtCore.Slot()
+    def _keymanager_import_ok(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_import_ok
 
-            logger.debug('Import ok')
+        Notify the user that the key import went OK.
+        """
+        QtGui.QMessageBox.information(
+            self, self.tr("Import Successful"),
+            self.tr("The key pair was imported successfully."))
 
-            QtGui.QMessageBox.information(
-                self, self.tr("Import Successful"),
-                self.tr("The key pair was imported successfully."))
-        else:
-            logger.debug('Import canceled by the user.')
+    @QtCore.Slot()
+    def _import_ioerror(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_import_ioerror
+
+        Notify the user that the key import had an IOError problem.
+        """
+        QtGui.QMessageBox.critical(
+            self, self.tr("Input/Output error"),
+            self.tr("There was an error accessing the file.\n"
+                    "Import canceled."))
+
+    @QtCore.Slot()
+    def _import_datamismatch(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_import_datamismatch
+
+        Notify the user that the key import had an data mismatch problem.
+        """
+        QtGui.QMessageBox.warning(
+            self, self.tr("Data mismatch"),
+            self.tr("The public and private key should have the "
+                    "same address and fingerprint.\n"
+                    "Import canceled."))
+
+    @QtCore.Slot()
+    def _import_missingkey(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_import_missingkey
+
+        Notify the user that the key import failed due a missing key.
+        """
+        QtGui.QMessageBox.warning(
+            self, self.tr("Missing key"),
+            self.tr("You need to provide the public AND private "
+                    "key in the same file.\n"
+                    "Import canceled."))
+
+    @QtCore.Slot()
+    def _import_addressmismatch(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_import_addressmismatch
+
+        Notify the user that the key import failed due an address mismatch.
+        """
+        QtGui.QMessageBox.warning(
+            self, self.tr("Address mismatch"),
+            self.tr("The identity for the key needs to be the same "
+                    "as your user address.\n"
+                    "Import canceled."))
 
     def _export_keys(self):
         """
         Exports the user's key pair.
         """
-        fileName, filtr = QtGui.QFileDialog.getSaveFileName(
+        file_name, filtr = QtGui.QFileDialog.getSaveFileName(
             self, self.tr("Save keys file"),
             options=QtGui.QFileDialog.DontUseNativeDialog)
 
-        if fileName:
-            try:
-                with open(fileName, 'w') as keys_file:
-                    keys_file.write(self._key.key_data)
-                    keys_file.write(self._key_priv.key_data)
-
-                logger.debug('Export ok')
-                QtGui.QMessageBox.information(
-                    self, self.tr("Export Successful"),
-                    self.tr("The key pair was exported successfully.\n"
-                            "Please, store your private key in a safe place."))
-            except IOError as e:
-                logger.error("IOError exporting key. {0!r}".format(e))
-                QtGui.QMessageBox.critical(
-                    self, self.tr("Input/Output error"),
-                    self.tr("There was an error accessing the file.\n"
-                            "Export canceled."))
-                return
+        if file_name:
+            self._backend.keymanager_export_keys(self._user, file_name)
         else:
             logger.debug('Export canceled by the user.')
 
-    def _list_keys(self):
+    @QtCore.Slot()
+    def _keymanager_export_ok(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_export_ok
+
+        Notify the user that the key export went OK.
         """
-        Loads all the public keys stored in the local db to the keys table.
+        QtGui.QMessageBox.information(
+            self, self.tr("Export Successful"),
+            self.tr("The key pair was exported successfully.\n"
+                    "Please, store your private key in a safe place."))
+
+    @QtCore.Slot()
+    def _keymanager_export_error(self):
+        """
+        TRIGGERS:
+            Signaler.keymanager_export_error
+
+        Notify the user that the key export didn't go well.
+        """
+        QtGui.QMessageBox.critical(
+            self, self.tr("Input/Output error"),
+            self.tr("There was an error accessing the file.\n"
+                    "Export canceled."))
+
+    @QtCore.Slot()
+    def _keymanager_keys_list(self, keys):
         """
-        keys = self._keymanager.get_all_keys_in_local_db()
+        TRIGGERS:
+            Signaler.keymanager_keys_list
 
+        Load the keys given as parameter in the table.
+
+        :param keys: the list of keys to load.
+        :type keys: list
+        """
         keys_table = self.ui.twPublicKeys
+
         for key in keys:
             row = keys_table.rowCount()
             keys_table.insertRow(row)
             keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))
             keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id))
+
+    def _backend_connect(self):
+        """
+        Connect to backend signals.
+        """
+        sig = self._backend.signaler
+
+        sig.keymanager_export_ok.connect(self._keymanager_export_ok)
+        sig.keymanager_export_error.connect(self._keymanager_export_error)
+        sig.keymanager_keys_list.connect(self._keymanager_keys_list)
+
+        sig.keymanager_key_details.connect(self._keymanager_key_details)
+
+        sig.keymanager_import_ok.connect(self._keymanager_import_ok)
+
+        sig.keymanager_import_ioerror.connect(self._import_ioerror)
+        sig.keymanager_import_datamismatch.connect(self._import_datamismatch)
+        sig.keymanager_import_missingkey.connect(self._import_missingkey)
+        sig.keymanager_import_addressmismatch.connect(
+            self._import_addressmismatch)
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index ca28b8bf2f0043865d2e361c378933ae9e7737e9..8b9f2d443cacc22ce32f63a08d33cee3716b39d1 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -24,7 +24,7 @@ from functools import partial
 
 from PySide import QtCore, QtGui
 
-from leap.bitmask.services.eip.connection import EIPConnection
+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
@@ -32,6 +32,7 @@ from leap.common.check import leap_assert_type
 
 from ui_eip_status import Ui_EIPStatus
 
+QtDelayedCall = QtCore.QTimer.singleShot
 logger = logging.getLogger(__name__)
 
 
@@ -43,9 +44,14 @@ class EIPStatusWidget(QtGui.QWidget):
     RATE_STR = "%1.2f KB/s"
     TOTAL_STR = "%1.2f Kb"
 
-    eip_connection_connected = QtCore.Signal()
+    def __init__(self, parent=None, eip_conductor=None):
+        """
+        :param parent: the parent of the widget.
+        :type parent: QObject
 
-    def __init__(self, parent=None):
+        :param eip_conductor: an EIPConductor object.
+        :type eip_conductor: EIPConductor
+        """
         QtGui.QWidget.__init__(self, parent)
 
         self._systray = None
@@ -54,13 +60,17 @@ class EIPStatusWidget(QtGui.QWidget):
         self.ui = Ui_EIPStatus()
         self.ui.setupUi(self)
 
-        self.eipconnection = EIPConnection()
+        self.eip_conductor = eip_conductor
+        self.eipconnection = eip_conductor.eip_connection
 
         # set systray tooltip status
         self._eip_status = ""
         self._service_name = get_service_display_name(EIP_SERVICE)
 
         self.ui.eip_bandwidth.hide()
+        self.hide_fw_down_button()
+        self.ui.btnFwDown.clicked.connect(
+            self._on_fw_down_button_clicked)
 
         # Set the EIP status icons
         self.CONNECTING_ICON = None
@@ -75,11 +85,43 @@ class EIPStatusWidget(QtGui.QWidget):
         self._make_status_clickable()
 
         self._provider = ""
+        self.is_restart = False
+        self.is_cold_start = True
 
         # Action for the systray
         self._eip_disabled_action = QtGui.QAction(
             "{0} is {1}".format(self._service_name, self.tr("disabled")), self)
 
+    def connect_backend_signals(self):
+        """
+        Connect backend signals.
+        """
+        signaler = self.eip_conductor._backend.signaler
+
+        signaler.eip_openvpn_already_running.connect(
+            self._on_eip_openvpn_already_running)
+        signaler.eip_alien_openvpn_already_running.connect(
+            self._on_eip_alien_openvpn_already_running)
+        signaler.eip_openvpn_not_found_error.connect(
+            self._on_eip_openvpn_not_found_error)
+        signaler.eip_vpn_launcher_exception.connect(
+            self._on_eip_vpn_launcher_exception)
+        signaler.eip_no_polkit_agent_error.connect(
+            self._on_eip_no_polkit_agent_error)
+        signaler.eip_connection_aborted.connect(
+            self._on_eip_connection_aborted)
+        signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error)
+        signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error)
+
+        signaler.eip_state_changed.connect(self.update_vpn_state)
+        signaler.eip_status_changed.connect(self.update_vpn_status)
+
+        # 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)
+
     def _make_status_clickable(self):
         """
         Makes upload and download figures clickable.
@@ -208,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget):
 
     def set_action_eip_startstop(self, action_eip_startstop):
         """
-        Sets the action_eip_startstop to use.
+        Set the action_eip_startstop to use.
 
         :param action_eip_startstop: action_eip_status to be used
         :type action_eip_startstop: QtGui.QAction
@@ -238,9 +280,11 @@ class EIPStatusWidget(QtGui.QWidget):
     def eip_pre_up(self):
         """
         Triggered when the app activates eip.
-        Hides the status box and disables the start/stop button.
+        Disables the start/stop button.
         """
         self.set_startstop_enabled(False)
+        msg = self.tr("Encrypted Internet is starting")
+        self.set_eip_message(msg)
 
     @QtCore.Slot()
     def disable_eip_start(self):
@@ -248,7 +292,7 @@ class EIPStatusWidget(QtGui.QWidget):
         Triggered when a default provider_config has not been found.
         Disables the start button and adds instructions to the user.
         """
-        #logger.debug('Hiding EIP start button')
+        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.
         # probably the best thing would be to make a conditional
@@ -282,10 +326,19 @@ class EIPStatusWidget(QtGui.QWidget):
         if self.isVisible():
             self._eip_status_menu.menuAction().setVisible(True)
 
-    # XXX disable (later) --------------------------
+    def set_eip_message(self, message):
+        """
+        Set the EIP Widget main message.
+
+        :param message: the message to set in the widget
+        :type message: str or unicode
+        """
+        self.ui.lblEIPMessage.setText(message)
+        self.ui.lblEIPMessage.show()
+
     def set_eip_status(self, status, error=False):
         """
-        Sets the status label at the VPN stage to status
+        Set the status label at the VPN stage to status.
 
         :param status: status message
         :type status: str or unicode
@@ -326,29 +379,80 @@ class EIPStatusWidget(QtGui.QWidget):
         Sets the state of the widget to how it should look after EIP
         has started
         """
-        self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))
         self.ui.btnEipStartStop.disconnect(self)
         self.ui.btnEipStartStop.clicked.connect(
             self.eipconnection.qtsigs.do_connect_signal)
 
-    # XXX disable -----------------------------
-    def eip_stopped(self):
+    def hide_fw_down_button(self):
+        """
+        Hide firewall-down button.
+        """
+        self.ui.btnFwDown.hide()
+
+    def show_fw_down_button(self):
+        """
+        Enable firewall-down button.
         """
+        retry_msg = self.tr("Retry")
+        self.ui.btnEipStartStop.setText(retry_msg)
+        self._action_eip_startstop.setText(retry_msg)
+        self.ui.btnFwDown.show()
+
+    def _on_fw_down_button_clicked(self):
+        """
+        Raise a signal for tearing down the firewall, and hide the button
+        afterwards.
+        """
+        self.eip_conductor._backend.tear_fw_down()
+        QtDelayedCall(50, self.hide_fw_down_button)
+
+        # XXX do actual check
+        msg = "Traffic is being routed in the clear."
+        self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
+        self.set_eip_message(msg)
+        self.set_eip_status("")
+
+    @QtCore.Slot(dict)
+    def eip_stopped(self, restart=False, failed=False):
+        """
+        TRIGGERS:
+            EIPConductor.qtsigs.disconnected_signal
+
         Sets the state of the widget to how it should look after EIP
         has stopped
         """
-        # XXX should connect this to EIPConnection.disconnected_signal
+        self.set_country_code("")
         self._reset_traffic_rates()
-        # XXX disable -----------------------------
-        self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
-        self.ui.btnEipStartStop.disconnect(self)
-        self.ui.btnEipStartStop.clicked.connect(
-            self.eipconnection.qtsigs.do_disconnect_signal)
-
         self.ui.eip_bandwidth.hide()
-        self.ui.lblEIPMessage.setText(
-            self.tr("Traffic is being routed in the clear"))
+
+        # This is assuming the firewall works correctly, but we should test fw
+        # status positively.
+        # Or better call it from the conductor...
+
+        clear_traffic = self.tr("Traffic is being routed in the clear.")
+        unreachable_net = self.tr("Network is unreachable.")
+        failed_msg = self.tr("Error connecting")
+
+        if restart:
+            msg = unreachable_net
+        elif failed:
+            msg = failed_msg
+        else:
+            msg = clear_traffic
+        self.set_eip_message(msg)
+        self.ui.lblEIPStatus.show()
+        self.show()
+
+    def eip_failed_to_connect(self):
+        """
+        Update EIP messages with error during (re)connection.
+        """
+        msg = self.tr("Error connecting.")
+        self.ui.lblEIPMessage.setText(msg)
         self.ui.lblEIPStatus.show()
+        self.set_eip_status(self.tr("Bitmask is blocking "
+                                    "unencrypted traffic."))
+        self.show_fw_down_button()
 
     @QtCore.Slot(dict)
     def update_vpn_status(self, data=None):
@@ -407,11 +511,20 @@ class EIPStatusWidget(QtGui.QWidget):
             self.ui.lblEIPStatus.hide()
 
             # XXX should be handled by the state machine too.
-            self.eip_connection_connected.emit()
+            # --- is this currently being sent?
+            self.eipconnection.qtsigs.connected_signal.emit()
+            self._on_eip_connected()
+            self.is_cold_start = False
 
         # XXX should lookup vpn_state map in EIPConnection
         elif vpn_state == "AUTH":
             self.set_eip_status(self.tr("Authenticating..."))
+            # 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":
@@ -423,10 +536,11 @@ class EIPStatusWidget(QtGui.QWidget):
         elif vpn_state == "ALREADYRUNNING":
             # Put the following calls in Qt's event queue, otherwise
             # the UI won't update properly
-            QtCore.QTimer.singleShot(
-                0, self.eipconnection.qtsigs.do_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.")
-            QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))
+            QtDelayedCall(0, partial(self.set_eip_status, msg))
         else:
             self.set_eip_status(vpn_state)
 
@@ -468,5 +582,152 @@ class EIPStatusWidget(QtGui.QWidget):
 
     def set_provider(self, provider):
         self._provider = provider
+
         self.ui.lblEIPMessage.setText(
-            self.tr("Route traffic through: {0}").format(self._provider))
+            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)
+
+    def set_country_code(self, code):
+        """
+        Set the pixmap of the given country code
+
+        :param code: the country code
+        :type code: str
+        """
+        if code is not None and len(code) == 2:
+            img = ":/images/countries/%s.png" % (code.lower(),)
+        else:
+            img = None
+        cc = self.ui.lblGatewayCountryCode
+        cc.setPixmap(QtGui.QPixmap(img))
+        cc.setToolTip(code)
+
+    def aborted(self):
+        """
+        Notify the state machine that EIP was aborted for some reason.
+        """
+        # signal connection_aborted to state machine:
+        qtsigs = self.eipconnection.qtsigs
+        qtsigs.connection_aborted_signal.emit()
+
+    #
+    # Slots for signals
+    #
+
+    @QtCore.Slot()
+    def _on_eip_connection_aborted(self):
+        """
+        TRIGGERS:
+            Signaler.eip_connection_aborted
+        """
+        # TODO this name is very misleading, since there's a generic signal
+        # that's called connection_aborted / connection_died...
+        # should rename to something more specific about missing config.
+        logger.error("Tried to start EIP but cannot find any "
+                     "available provider!")
+
+        eip_status_label = self.tr("Could not load {0} configuration.")
+        eip_status_label = eip_status_label.format(
+            self.eip_conductor.eip_name)
+        self.set_eip_status(eip_status_label, error=True)
+
+        self.aborted()
+
+    def _on_eip_openvpn_already_running(self):
+        self.set_eip_status(
+            self.tr("Another openvpn instance is already running, and "
+                    "could not be stopped."),
+            error=True)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_alien_openvpn_already_running(self):
+        self.set_eip_status(
+            self.tr("Another openvpn instance is already running, and "
+                    "could not be stopped because it was not launched by "
+                    "Bitmask. Please stop it and try again."),
+            error=True)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_openvpn_not_found_error(self):
+        self.set_eip_status(
+            self.tr("We could not find openvpn binary."),
+            error=True)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    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)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_no_polkit_agent_error(self):
+        self.set_eip_status(
+            # XXX this should change to polkit-kde where
+            # applicable.
+            self.tr("We could not find any authentication agent in your "
+                    "system.<br/>Make sure you have"
+                    "<b>polkit-gnome-authentication-agent-1</b> running and"
+                    "try again."),
+            error=True)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_no_pkexec_error(self):
+        self.set_eip_status(
+            self.tr("We could not find <b>pkexec</b> in your system."),
+            error=True)
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_no_tun_kext_error(self):
+        self.set_eip_status(
+            self.tr("{0} cannot be started because the tuntap extension is "
+                    "not installed properly in your "
+                    "system.").format(self.eip_conductor.eip_name))
+        self.set_eipstatus_off()
+
+        self.aborted()
+
+    def _on_eip_connected(self):
+        """
+        Reconnect the disconnecting signal when we are just connected,
+        so that we restore the disconnecting -> stop behaviour.
+        """
+        self.eip_conductor.reconnect_stop_signal()
+
+    @QtCore.Slot()
+    def _on_eip_network_unreachable(self):
+        """
+        TRIGGERS:
+            self._eip_connection.qtsigs.network_unreachable
+
+        Displays a "network unreachable" error in the EIP status panel.
+        """
+        self.set_eip_status(self.tr("Network is unreachable"),
+                            error=True)
+        self.set_eip_status_icon("error")
+
+    def set_eipstatus_off(self, error=True):
+    # XXX this should be handled by the state machine.
+        """
+        Sets eip status to off
+        """
+        self.set_eip_status("", error=error)
+        self.set_eip_status_icon("error")
+
+import eipstatus_rc
+assert(eipstatus_rc)
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index f19b172fd5c19bd4eec49633517a6043ebdac8bd..3a8354b1d5a1831feebac0e26ac869b38203eac3 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -27,7 +27,7 @@ from twisted.internet import threads
 from ui_loggerwindow import Ui_LoggerWindow
 
 from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
 from leap.bitmask.util import pastebin
 from leap.common.check import leap_assert, leap_assert_type
 
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index ac7ad878ba8a888865001ac8ae511df0775ebf15..f66e71d96ab23b27e5489ad1c605eed393ccbabb 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -24,6 +24,7 @@ from ui_login import Ui_LoginWidget
 
 from leap.bitmask.config import flags
 from leap.bitmask.util import make_address
+from leap.bitmask.util.credentials import USERNAME_REGEX
 from leap.bitmask.util.keyring_helpers import has_keyring
 from leap.bitmask.util.keyring_helpers import get_keyring
 from leap.common.check import leap_assert_type
@@ -48,8 +49,6 @@ class LoginWidget(QtGui.QWidget):
 
     MAX_STATUS_WIDTH = 40
 
-    BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
-
     # Keyring
     KEYRING_KEY = "bitmask"
 
@@ -87,7 +86,7 @@ class LoginWidget(QtGui.QWidget):
         self.ui.btnLogout.clicked.connect(
             self.logout)
 
-        username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+        username_re = QtCore.QRegExp(USERNAME_REGEX)
         self.ui.lnUser.setValidator(
             QtGui.QRegExpValidator(username_re, self))
 
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index d3346780afece3ca82d38978a2c6c2afeff138ce..5caef7458df661e81c82148176180cb5249c32a3 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget):
     def set_soledad_failed(self):
         """
         TRIGGERS:
-            SoledadBootstrapper.soledad_failed
+            Signaler.soledad_bootstrap_failed
 
         This method is called whenever soledad has a failure.
         """
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index e3848c465b17f6465223b5553ccb7f0d2f523a31..3ef994b14ce7e6f653178f62d5c0ebec1ccb2c1f 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -19,22 +19,17 @@ Main window for Bitmask.
 """
 import logging
 import socket
-import time
 
-from threading import Condition
 from datetime import datetime
 
 from PySide import QtCore, QtGui
-from zope.proxy import ProxyBase, setProxiedObject
 from twisted.internet import reactor, threads
 
 from leap.bitmask import __version__ as VERSION
 from leap.bitmask import __version_hash__ as VERSION_HASH
 from leap.bitmask.config import flags
 from leap.bitmask.config.leapsettings import LeapSettings
-from leap.bitmask.config.providerconfig import ProviderConfig
 
-from leap.bitmask.gui import statemachines
 from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement
 from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow
 from leap.bitmask.gui.eip_status import EIPStatusWidget
@@ -45,30 +40,24 @@ from leap.bitmask.gui.preferenceswindow import PreferencesWindow
 from leap.bitmask.gui.systray import SysTray
 from leap.bitmask.gui.wizard import Wizard
 
-from leap.bitmask import provider
 from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
 from leap.bitmask.platform_init.initializers import init_platform
 
 from leap.bitmask import backend
 
-from leap.bitmask.services import get_service_display_name
-
+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.services.eip.connection import EIPConnection
-from leap.bitmask.services.soledad.soledadbootstrapper import \
-    SoledadBootstrapper
 
 from leap.bitmask.util import make_address
 from leap.bitmask.util.keyring_helpers import has_keyring
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
 
 if IS_WIN:
     from leap.bitmask.platform_init.locks import WindowsLock
     from leap.bitmask.platform_init.locks import raise_window_ack
 
-from leap.common.check import leap_assert
 from leap.common.events import register
 from leap.common.events import events_pb2 as proto
 
@@ -76,6 +65,7 @@ from leap.mail.imap.service.imap import IMAP_PORT
 
 from ui_mainwindow import Ui_MainWindow
 
+QtDelayedCall = QtCore.QTimer.singleShot
 logger = logging.getLogger(__name__)
 
 
@@ -89,17 +79,17 @@ class MainWindow(QtGui.QMainWindow):
     new_updates = QtCore.Signal(object)
     raise_window = QtCore.Signal([])
     soledad_ready = QtCore.Signal([])
-    mail_client_logged_in = QtCore.Signal([])
     logout = QtCore.Signal([])
+    all_services_stopped = QtCore.Signal()
 
     # We use this flag to detect abnormal terminations
     user_stopped_eip = False
 
     # We give EIP some time to come up before starting soledad anyway
-    EIP_TIMEOUT = 60000  # in milliseconds
+    EIP_START_TIMEOUT = 60000  # in milliseconds
 
-    # We give each service some time to come to a halt before forcing quit
-    SERVICE_STOP_TIMEOUT = 20
+    # We give the services some time to a halt before forcing quit.
+    SERVICES_STOP_TIMEOUT = 20
 
     def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):
         """
@@ -125,9 +115,6 @@ class MainWindow(QtGui.QMainWindow):
         register(signal=proto.RAISE_WINDOW,
                  callback=self._on_raise_window_event,
                  reqcbk=lambda req, resp: None)  # make rpc call async
-        register(signal=proto.IMAP_CLIENT_LOGIN,
-                 callback=self._on_mail_client_logged_in,
-                 reqcbk=lambda req, resp: None)  # make rpc call async
         # end register leap events ####################################
 
         self._quit_callback = quit_callback
@@ -142,11 +129,16 @@ class MainWindow(QtGui.QMainWindow):
 
         self._settings = LeapSettings()
 
+        # Login Widget
         self._login_widget = LoginWidget(
             self._settings,
             self)
         self.ui.loginLayout.addWidget(self._login_widget)
 
+        # Mail Widget
+        self._mail_status = MailStatusWidget(self)
+        self.ui.mailLayout.addWidget(self._mail_status)
+
         # Qt Signal Connections #####################################
         # TODO separate logic from ui signals.
 
@@ -155,67 +147,45 @@ class MainWindow(QtGui.QMainWindow):
         self._login_widget.show_wizard.connect(self._launch_wizard)
         self._login_widget.logout.connect(self._logout)
 
-        self._eip_status = EIPStatusWidget(self)
-        self.ui.eipLayout.addWidget(self._eip_status)
-        self._login_widget.logged_in_signal.connect(
-            self._eip_status.enable_eip_start)
-        self._login_widget.logged_in_signal.connect(
-            self._enable_eip_start_action)
+        # EIP Control redux #########################################
+        self._eip_conductor = eip_conductor.EIPConductor(
+            self._settings, self._backend)
+        self._eip_status = EIPStatusWidget(self, self._eip_conductor)
 
-        self._mail_status = MailStatusWidget(self)
-        self.ui.mailLayout.addWidget(self._mail_status)
-
-        self._eip_connection = EIPConnection()
-
-        # XXX this should be handled by EIP Conductor
-        self._eip_connection.qtsigs.connecting_signal.connect(
-            self._start_EIP)
-        self._eip_connection.qtsigs.disconnecting_signal.connect(
-            self._stop_eip)
+        self.ui.eipLayout.addWidget(self._eip_status)
+        self._eip_conductor.add_eip_widget(self._eip_status)
 
-        self._eip_status.eip_connection_connected.connect(
+        self._eip_conductor.connect_signals()
+        self._eip_conductor.qtsigs.connected_signal.connect(
             self._on_eip_connection_connected)
-        self._eip_status.eip_connection_connected.connect(
+        self._eip_conductor.qtsigs.connected_signal.connect(
             self._maybe_run_soledad_setup_checks)
+
         self.offline_mode_bypass_login.connect(
             self._maybe_run_soledad_setup_checks)
 
         self.eip_needs_login.connect(self._eip_status.disable_eip_start)
         self.eip_needs_login.connect(self._disable_eip_start_action)
 
+        self._already_started_eip = False
         self._trying_to_start_eip = False
 
-        # This is loaded only once, there's a bug when doing that more
-        # than once
-        # XXX HACK!! But we need it as long as we are using
-        # provider_config in here
-        self._provider_config = self._backend.get_provider_config()
-
-        # Used for automatic start of EIP
-        self._provisional_provider_config = ProviderConfig()
-
         self._already_started_eip = False
-        self._already_started_soledad = False
+        self._soledad_started = False
 
         # This is created once we have a valid provider config
         self._srp_auth = None
         self._logged_user = None
         self._logged_in_offline = False
 
-        self._backend_connected_signals = {}
-        self._backend_connect()
+        # Set used to track the services being stopped and need wait.
+        self._services_being_stopped = {}
 
-        self._soledad_bootstrapper = SoledadBootstrapper()
-        self._soledad_bootstrapper.download_config.connect(
-            self._soledad_intermediate_stage)
-        self._soledad_bootstrapper.gen_key.connect(
-            self._soledad_bootstrapped_stage)
-        self._soledad_bootstrapper.local_only_ready.connect(
-            self._soledad_bootstrapped_stage)
-        self._soledad_bootstrapper.soledad_invalid_auth_token.connect(
-            self._mail_status.set_soledad_invalid_auth_token)
-        self._soledad_bootstrapper.soledad_failed.connect(
-            self._mail_status.set_soledad_failed)
+        # timeout object used to trigger quit
+        self._quit_timeout_callater = None
+
+        self._backend_connected_signals = []
+        self._backend_connect()
 
         self.ui.action_preferences.triggered.connect(self._show_preferences)
         self.ui.action_eip_preferences.triggered.connect(
@@ -241,8 +211,7 @@ class MainWindow(QtGui.QMainWindow):
 
         self._systray = None
 
-        # XXX separate actions into a different
-        # module.
+        # XXX separate actions into a different module.
         self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self)
         self._mail_status.set_action_mail_status(self._action_mail_status)
 
@@ -260,6 +229,8 @@ class MainWindow(QtGui.QMainWindow):
         self._ui_mx_visible = True
         self._ui_eip_visible = True
 
+        self._provider_details = None
+
         # last minute UI manipulations
 
         self._center_window()
@@ -280,10 +251,6 @@ 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)
-        self.mail_client_logged_in.connect(self._fetch_incoming_mail)
-        self.logout.connect(self._stop_imap_service)
-        self.logout.connect(self._stop_smtp_service)
-
         ################################# end Qt Signals connection ########
 
         init_platform()
@@ -296,18 +263,11 @@ class MainWindow(QtGui.QMainWindow):
         self._bypass_checks = bypass_checks
         self._start_hidden = start_hidden
 
-        # We initialize Soledad and Keymanager instances as
-        # transparent proxies, so we can pass the reference freely
-        # around.
-        self._soledad = ProxyBase(None)
-        self._keymanager = ProxyBase(None)
-
-        self._soledad_defer = None
-
-        self._mail_conductor = mail_conductor.MailConductor(
-            self._soledad, self._keymanager)
+        self._mail_conductor = mail_conductor.MailConductor(self._backend)
         self._mail_conductor.connect_mail_signals(self._mail_status)
 
+        self.logout.connect(self._mail_conductor.stop_mail_services)
+
         # Eip machine is a public attribute where the state machine for
         # the eip connection will be available to the different components.
         # Remember that this will not live in the  +1600LOC mainwindow for
@@ -315,20 +275,19 @@ class MainWindow(QtGui.QMainWindow):
         # the EIPConductor or some other clever component that we will
         # instantiate from here.
 
-        self.eip_machine = None
         # start event machines
-        self.start_eip_machine()
+        # TODO should encapsulate all actions into one object
+        self._eip_conductor.start_eip_machine(
+            action=self._action_eip_startstop)
         self._mail_conductor.start_mail_machine()
 
-        self._eip_name = get_service_display_name(EIP_SERVICE)
-
         if self._first_run():
             self._wizard_firstrun = True
             self._disconnect_and_untrack()
             self._wizard = Wizard(backend=self._backend,
                                   bypass_checks=bypass_checks)
             # Give this window time to finish init and then show the wizard
-            QtCore.QTimer.singleShot(1, self._launch_wizard)
+            QtDelayedCall(1, self._launch_wizard)
             self._wizard.accepted.connect(self._finish_init)
             self._wizard.rejected.connect(self._rejected_wizard)
         else:
@@ -357,7 +316,7 @@ class MainWindow(QtGui.QMainWindow):
         :param method: the method to call when the signal is triggered.
         :type method: callable, Slot or Signal
         """
-        self._backend_connected_signals[signal] = method
+        self._backend_connected_signals.append((signal, method))
         signal.connect(method)
 
     def _backend_bad_call(self, data):
@@ -370,97 +329,102 @@ class MainWindow(QtGui.QMainWindow):
         logger.error("Bad call to the backend:")
         logger.error(data)
 
-    def _backend_connect(self):
+    def _backend_connect(self, only_tracked=False):
         """
-        Helper to connect to backend signals
-        """
-        sig = self._backend.signaler
+        Connect to backend signals.
 
-        sig.backend_bad_call.connect(self._backend_bad_call)
+        We track some signals in order to disconnect them on demand.
+        For instance, in the wizard we need to connect to some signals that are
+        already connected in the mainwindow, so to avoid conflicts we do:
+            - disconnect signals needed in wizard (`_disconnect_and_untrack`)
+            - use wizard
+            - reconnect disconnected signals (we use the `only_tracked` param)
 
-        self._connect_and_track(sig.prov_name_resolution,
-                                self._intermediate_stage)
-        self._connect_and_track(sig.prov_https_connection,
-                                self._intermediate_stage)
-        self._connect_and_track(sig.prov_download_ca_cert,
-                                self._intermediate_stage)
+        :param only_tracked: whether or not we should connect only the signals
+                             that we are tracking to disconnect later.
+        :type only_tracked: bool
+        """
+        sig = self._backend.signaler
+        conntrack = self._connect_and_track
+        auth_err = self._authentication_error
 
-        self._connect_and_track(sig.prov_download_provider_info,
-                                self._load_provider_config)
-        self._connect_and_track(sig.prov_check_api_certificate,
-                                self._provider_config_loaded)
+        conntrack(sig.prov_name_resolution, self._intermediate_stage)
+        conntrack(sig.prov_https_connection, self._intermediate_stage)
+        conntrack(sig.prov_download_ca_cert, self._intermediate_stage)
+        conntrack(sig.prov_download_provider_info, self._load_provider_config)
+        conntrack(sig.prov_check_api_certificate, self._provider_config_loaded)
+        conntrack(sig.prov_check_api_certificate, self._get_provider_details)
 
-        self._connect_and_track(sig.prov_problem_with_provider,
-                                self._login_problem_provider)
+        conntrack(sig.prov_problem_with_provider, self._login_problem_provider)
+        conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)
 
-        self._connect_and_track(sig.prov_cancelled_setup,
-                                self._set_login_cancelled)
+        conntrack(sig.prov_get_details, self._provider_get_details)
 
         # Login signals
-        self._connect_and_track(sig.srp_auth_ok, self._authentication_finished)
+        conntrack(sig.srp_auth_ok, self._authentication_finished)
 
-        auth_error = (
-            lambda: self._authentication_error(self.tr("Unknown error.")))
-        self._connect_and_track(sig.srp_auth_error, auth_error)
+        auth_error = lambda: auth_err(self.tr("Unknown error."))
+        conntrack(sig.srp_auth_error, auth_error)
 
-        auth_server_error = (
-            lambda: self._authentication_error(
-                self.tr("There was a server problem with authentication.")))
-        self._connect_and_track(sig.srp_auth_server_error, auth_server_error)
+        auth_server_error = lambda: auth_err(self.tr(
+            "There was a server problem with authentication."))
+        conntrack(sig.srp_auth_server_error, auth_server_error)
 
-        auth_connection_error = (
-            lambda: self._authentication_error(
-                self.tr("Could not establish a connection.")))
-        self._connect_and_track(sig.srp_auth_connection_error,
-                                auth_connection_error)
+        auth_connection_error = lambda: auth_err(self.tr(
+            "Could not establish a connection."))
+        conntrack(sig.srp_auth_connection_error, auth_connection_error)
 
-        auth_bad_user_or_password = (
-            lambda: self._authentication_error(
-                self.tr("Invalid username or password.")))
-        self._connect_and_track(sig.srp_auth_bad_user_or_password,
-                                auth_bad_user_or_password)
+        auth_bad_user_or_password = lambda: auth_err(self.tr(
+            "Invalid username or password."))
+        conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password)
 
         # Logout signals
-        self._connect_and_track(sig.srp_logout_ok, self._logout_ok)
-        self._connect_and_track(sig.srp_logout_error, self._logout_error)
-
-        self._connect_and_track(sig.srp_not_logged_in_error,
-                                self._not_logged_in_error)
+        conntrack(sig.srp_logout_ok, self._logout_ok)
+        conntrack(sig.srp_logout_error, self._logout_error)
+        conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error)
 
         # EIP bootstrap signals
-        self._connect_and_track(sig.eip_config_ready,
-                                self._eip_intermediate_stage)
-        self._connect_and_track(sig.eip_client_certificate_ready,
-                                self._finish_eip_bootstrap)
+        conntrack(sig.eip_config_ready, self._eip_intermediate_stage)
+        conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap)
+
+        ###################################################
+        # Add tracked signals above this, untracked below!
+        ###################################################
+        if only_tracked:
+            return
 
         # We don't want to disconnect some signals so don't track them:
+
+        sig.backend_bad_call.connect(self._backend_bad_call)
+
         sig.prov_unsupported_client.connect(self._needs_update)
         sig.prov_unsupported_api.connect(self._incompatible_api)
+        sig.prov_get_all_services.connect(self._provider_get_all_services)
 
-        # EIP start signals
-        sig.eip_openvpn_already_running.connect(
-            self._on_eip_openvpn_already_running)
-        sig.eip_alien_openvpn_already_running.connect(
-            self._on_eip_alien_openvpn_already_running)
-        sig.eip_openvpn_not_found_error.connect(
-            self._on_eip_openvpn_not_found_error)
-        sig.eip_vpn_launcher_exception.connect(
-            self._on_eip_vpn_launcher_exception)
-        sig.eip_no_polkit_agent_error.connect(
-            self._on_eip_no_polkit_agent_error)
-        sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error)
-        sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error)
-
-        sig.eip_state_changed.connect(self._eip_status.update_vpn_state)
-        sig.eip_status_changed.connect(self._eip_status.update_vpn_status)
-        sig.eip_process_finished.connect(self._eip_finished)
-        sig.eip_network_unreachable.connect(self._on_eip_network_unreachable)
-        sig.eip_process_restart_tls.connect(self._do_eip_restart)
-        sig.eip_process_restart_ping.connect(self._do_eip_restart)
+        # EIP start signals ==============================================
 
+        self._eip_conductor.connect_backend_signals()
         sig.eip_can_start.connect(self._backend_can_start_eip)
         sig.eip_cannot_start.connect(self._backend_cannot_start_eip)
 
+        # ==================================================================
+
+        # Soledad signals
+        # TODO delegate connection to soledad bootstrapper
+        sig.soledad_bootstrap_failed.connect(
+            self._mail_status.set_soledad_failed)
+        sig.soledad_bootstrap_finished.connect(self._on_soledad_ready)
+
+        sig.soledad_offline_failed.connect(
+            self._mail_status.set_soledad_failed)
+        sig.soledad_offline_finished.connect(self._on_soledad_ready)
+
+        sig.soledad_invalid_auth_token.connect(
+            self._mail_status.set_soledad_invalid_auth_token)
+
+        # TODO: connect this with something
+        # sig.soledad_cancelled_bootstrap.connect()
+
     def _disconnect_and_untrack(self):
         """
         Helper to disconnect the tracked signals.
@@ -468,13 +432,13 @@ class MainWindow(QtGui.QMainWindow):
         Some signals are emitted from the wizard, and we want to
         ignore those.
         """
-        for signal, method in self._backend_connected_signals.items():
+        for signal, method in self._backend_connected_signals:
             try:
                 signal.disconnect(method)
             except RuntimeError:
                 pass  # Signal was not connected
 
-        self._backend_connected_signals = {}
+        self._backend_connected_signals = []
 
     @QtCore.Slot()
     def _rejected_wizard(self):
@@ -497,7 +461,7 @@ class MainWindow(QtGui.QMainWindow):
             # This happens if the user finishes the provider
             # setup but does not register
             self._wizard = None
-            self._backend_connect()
+            self._backend_connect(only_tracked=True)
             if self._wizard_firstrun:
                 self._finish_init()
 
@@ -586,13 +550,14 @@ class MainWindow(QtGui.QMainWindow):
         domain = self._login_widget.get_selected_provider()
         logged_user = "{0}@{1}".format(self._logged_user, domain)
 
-        has_mx = True
-        if self._logged_user is not None:
-            provider_config = self._get_best_provider_config()
-            has_mx = provider_config.provides_mx()
+        details = self._provider_details
+        mx_provided = False
+        if details is not None:
+            mx_provided = MX_SERVICE in details.services
 
-        akm = AdvancedKeyManagement(
-            self, has_mx, logged_user, self._keymanager, self._soledad)
+        # XXX: handle differently not logged in user?
+        akm = AdvancedKeyManagement(self, mx_provided, logged_user,
+                                    self._backend, self._soledad_started)
         akm.show()
 
     @QtCore.Slot()
@@ -604,11 +569,13 @@ class MainWindow(QtGui.QMainWindow):
 
         Displays the preferences window.
         """
-        user = self._login_widget.get_user()
-        prov = self._login_widget.get_selected_provider()
-        preferences = PreferencesWindow(
-            self, self._backend, self._provider_config, self._soledad,
-            user, prov)
+        user = self._logged_user
+        domain = self._login_widget.get_selected_provider()
+        mx_provided = False
+        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_ready.connect(preferences.set_soledad_ready)
         preferences.show()
@@ -630,7 +597,7 @@ class MainWindow(QtGui.QMainWindow):
         default_provider = settings.get_defaultprovider()
 
         if default_provider is None:
-            logger.warning("Trying toupdate eip enabled status but there's no"
+            logger.warning("Trying to update eip enabled status but there's no"
                            " default provider. Disabling EIP for the time"
                            " being...")
             self._backend_cannot_start_eip()
@@ -642,7 +609,7 @@ class MainWindow(QtGui.QMainWindow):
         # 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.setup_eip(default_provider, skip_network=True)
+            self._backend.eip_setup(default_provider, skip_network=True)
 
     def _backend_can_start_eip(self):
         """
@@ -670,7 +637,6 @@ class MainWindow(QtGui.QMainWindow):
                 # so the user needs to log in first
                 self._eip_status.disable_eip_start()
         else:
-            self._stop_eip()
             self._eip_status.disable_eip_start()
             self._eip_status.set_eip_status(self.tr("Disabled"))
 
@@ -697,7 +663,6 @@ class MainWindow(QtGui.QMainWindow):
             # so the user needs to log in first
             self._eip_status.disable_eip_start()
         else:
-            self._stop_eip()
             self._eip_status.disable_eip_start()
             self._eip_status.set_eip_status(self.tr("Disabled"))
 
@@ -817,7 +782,7 @@ class MainWindow(QtGui.QMainWindow):
                 self.eip_needs_login.emit()
 
             self._wizard = None
-            self._backend_connect()
+            self._backend_connect(only_tracked=True)
         else:
             self._update_eip_enabled_status()
 
@@ -846,16 +811,9 @@ class MainWindow(QtGui.QMainWindow):
         """
         providers = self._settings.get_configured_providers()
 
-        services = set()
-
-        for prov in providers:
-            provider_config = ProviderConfig()
-            loaded = provider_config.load(
-                provider.get_provider_path(prov))
-            if loaded:
-                for service in provider_config.get_services():
-                    services.add(service)
+        self._backend.provider_get_all_services(providers)
 
+    def _provider_get_all_services(self, services):
         self._set_eip_visible(EIP_SERVICE in services)
         self._set_mx_visible(MX_SERVICE in services)
 
@@ -893,14 +851,11 @@ class MainWindow(QtGui.QMainWindow):
         """
         Set the login label to reflect offline status.
         """
-        if self._logged_in_offline:
-            provider = ""
-        else:
+        provider = ""
+        if not self._logged_in_offline:
             provider = self.ui.lblLoginProvider.text()
 
-        self.ui.lblLoginProvider.setText(
-            provider +
-            self.tr(" (offline mode)"))
+        self.ui.lblLoginProvider.setText(provider + self.tr(" (offline mode)"))
 
     #
     # systray
@@ -923,7 +878,8 @@ class MainWindow(QtGui.QMainWindow):
         systrayMenu.addAction(self._action_visible)
         systrayMenu.addSeparator()
 
-        eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF"))
+        eip_status_label = "{0}: {1}".format(
+            self._eip_conductor.eip_name, self.tr("OFF"))
         self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label)
         eip_menu.addAction(self._action_eip_startstop)
         self._eip_status.set_eip_status_menu(eip_menu)
@@ -1005,7 +961,7 @@ class MainWindow(QtGui.QMainWindow):
 
         # Wait a bit until the window visibility has changed so
         # the menu is set with the correct value.
-        QtCore.QTimer.singleShot(500, self._update_hideshow_menu)
+        QtDelayedCall(500, self._update_hideshow_menu)
 
     def _center_window(self):
         """
@@ -1154,9 +1110,8 @@ class MainWindow(QtGui.QMainWindow):
         provider configuration if it's not present, otherwise will
         emit the corresponding signals inmediately
         """
-        # XXX should rename this provider, name clash.
-        provider = self._login_widget.get_selected_provider()
-        self._backend.setup_provider(provider)
+        domain = self._login_widget.get_selected_provider()
+        self._backend.provider_setup(domain)
 
     @QtCore.Slot(dict)
     def _load_provider_config(self, data):
@@ -1164,12 +1119,11 @@ class MainWindow(QtGui.QMainWindow):
         TRIGGERS:
             self._backend.signaler.prov_download_provider_info
 
-        Once the provider config has been downloaded, this loads the
-        self._provider_config instance with it and starts the second
-        part of the bootstrapping sequence
+        Once the provider config has been downloaded, start the second
+        part of the bootstrapping sequence.
 
         :param data: result from the last stage of the
-                     run_provider_select_checks
+                     backend.provider_setup()
         :type data: dict
         """
         if data[self._backend.PASSED_KEY]:
@@ -1211,7 +1165,6 @@ class MainWindow(QtGui.QMainWindow):
             self._set_label_offline()
             self.offline_mode_bypass_login.emit()
         else:
-            leap_assert(self._provider_config, "We need a provider config")
             self.ui.action_create_new_account.setEnabled(False)
             if self._login_widget.start_login():
                 self._download_provider_config()
@@ -1250,20 +1203,19 @@ class MainWindow(QtGui.QMainWindow):
         Cancel the running defers to avoid app blocking.
         """
         # XXX: Should we stop all the backend defers?
-        self._backend.cancel_setup_provider()
-        self._backend.cancel_login()
+        self._backend.provider_cancel_setup()
+        self._backend.user_cancel_login()
+        self._backend.soledad_cancel_bootstrap()
+        self._backend.soledad_close()
 
-        if self._soledad_defer is not None:
-            logger.debug("Cancelling soledad defer.")
-            self._soledad_defer.cancel()
-            self._soledad_defer = None
+        self._soledad_started = False
 
     @QtCore.Slot()
     def _set_login_cancelled(self):
         """
         TRIGGERS:
             Signaler.prov_cancelled_setup fired by
-            self._backend.cancel_setup_provider()
+            self._backend.provider_cancel_setup()
 
         This method re-enables the login widget and display a message for
         the cancelled operation.
@@ -1280,16 +1232,14 @@ class MainWindow(QtGui.QMainWindow):
         Once the provider configuration is loaded, this starts the SRP
         authentication
         """
-        leap_assert(self._provider_config, "We need a provider config!")
-
         if data[self._backend.PASSED_KEY]:
             username = self._login_widget.get_user()
             password = self._login_widget.get_password()
 
             self._show_hide_unsupported_services()
 
-            domain = self._provider_config.get_domain()
-            self._backend.login(domain, username, password)
+            domain = self._login_widget.get_selected_provider()
+            self._backend.user_login(domain, username, password)
         else:
             logger.error(data[self._backend.ERROR_KEY])
             self._login_problem_provider()
@@ -1307,7 +1257,7 @@ class MainWindow(QtGui.QMainWindow):
 
         self._logged_user = self._login_widget.get_user()
         user = self._logged_user
-        domain = self._provider_config.get_domain()
+        domain = self._login_widget.get_selected_provider()
         full_user_id = make_address(user, domain)
         self._mail_conductor.userid = full_user_id
         self._start_eip_bootstrap()
@@ -1317,11 +1267,11 @@ class MainWindow(QtGui.QMainWindow):
         if MX_SERVICE in self._enabled_services:
             btn_enabled = self._login_widget.set_logout_btn_enabled
             btn_enabled(False)
-            self.soledad_ready.connect(lambda: btn_enabled(True))
-            self._soledad_bootstrapper.soledad_failed.connect(
-                lambda: btn_enabled(True))
+            sig = self._backend.signaler
+            sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True))
+            sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True))
 
-        if not self._get_best_provider_config().provides_mx():
+        if not MX_SERVICE in self._provider_details.services:
             self._set_mx_visible(False)
 
     def _start_eip_bootstrap(self):
@@ -1331,11 +1281,10 @@ class MainWindow(QtGui.QMainWindow):
         """
 
         self._login_widget.logged_in()
-        provider = self._provider_config.get_domain()
-        self.ui.lblLoginProvider.setText(provider)
+        domain = self._login_widget.get_selected_provider()
+        self.ui.lblLoginProvider.setText(domain)
 
-        self._enabled_services = self._settings.get_enabled_services(
-            self._provider_config.get_domain())
+        self._enabled_services = self._settings.get_enabled_services(domain)
 
         # TODO separate UI from logic.
         if self._provides_mx_and_enabled():
@@ -1345,6 +1294,30 @@ class MainWindow(QtGui.QMainWindow):
 
         self._maybe_start_eip()
 
+    @QtCore.Slot()
+    def _get_provider_details(self):
+        """
+        TRIGGERS:
+            prov_check_api_certificate
+
+        Set the attributes to know if the EIP and MX services are supported
+        and enabled.
+        This is triggered right after the provider has been set up.
+        """
+        domain = self._login_widget.get_selected_provider()
+        lang = QtCore.QLocale.system().name()
+        self._backend.provider_get_details(domain, lang)
+
+    @QtCore.Slot()
+    def _provider_get_details(self, details):
+        """
+        Set the details for the just downloaded provider.
+
+        :param details: the details of the provider.
+        :type details: ProviderConfigLight
+        """
+        self._provider_details = details
+
     def _provides_mx_and_enabled(self):
         """
         Defines if the current provider provides mx and if we have it enabled.
@@ -1352,9 +1325,15 @@ class MainWindow(QtGui.QMainWindow):
         :returns: True if provides and is enabled, False otherwise
         :rtype: bool
         """
-        provider_config = self._get_best_provider_config()
-        return (provider_config.provides_mx() and
-                MX_SERVICE in self._enabled_services)
+        domain = self._login_widget.get_selected_provider()
+        enabled_services = self._settings.get_enabled_services(domain)
+
+        mx_enabled = MX_SERVICE in enabled_services
+        mx_provided = False
+        if self._provider_details is not None:
+            mx_provided = MX_SERVICE in self._provider_details.services
+
+        return mx_enabled and mx_provided
 
     def _provides_eip_and_enabled(self):
         """
@@ -1363,33 +1342,30 @@ class MainWindow(QtGui.QMainWindow):
         :returns: True if provides and is enabled, False otherwise
         :rtype: bool
         """
-        provider_config = self._get_best_provider_config()
-        return (provider_config.provides_eip() and
-                EIP_SERVICE in self._enabled_services)
+        domain = self._login_widget.get_selected_provider()
+        enabled_services = self._settings.get_enabled_services(domain)
+
+        eip_enabled = EIP_SERVICE in enabled_services
+        eip_provided = False
+        if self._provider_details is not None:
+            eip_provided = EIP_SERVICE in self._provider_details.services
+
+        return eip_enabled and eip_provided
 
     def _maybe_run_soledad_setup_checks(self):
         """
         Conditionally start Soledad.
         """
         # TODO split.
-        if self._already_started_soledad is True:
-            return
-
-        if not self._provides_mx_and_enabled():
+        if not self._provides_mx_and_enabled() and not flags.OFFLINE:
+            logger.debug("Provider does not offer MX, but it is enabled.")
             return
 
         username = self._login_widget.get_user()
         password = unicode(self._login_widget.get_password())
         provider_domain = self._login_widget.get_selected_provider()
 
-        sb = self._soledad_bootstrapper
-        if flags.OFFLINE is True:
-            provider_domain = self._login_widget.get_selected_provider()
-            sb._password = password
-
-            self._provisional_provider_config.load(
-                provider.get_provider_path(provider_domain))
-
+        if flags.OFFLINE:
             full_user_id = make_address(username, provider_domain)
             uuid = self._settings.get_uuid(full_user_id)
             self._mail_conductor.userid = full_user_id
@@ -1399,74 +1375,26 @@ class MainWindow(QtGui.QMainWindow):
                 # this is mostly for internal use/debug for now.
                 logger.warning("Sorry! Log-in at least one time.")
                 return
-            fun = sb.load_offline_soledad
-            fun(full_user_id, password, uuid)
+            self._backend.soledad_load_offline(full_user_id, password, uuid)
         else:
-            provider_config = self._provider_config
-
             if self._logged_user is not None:
-                self._soledad_defer = sb.run_soledad_setup_checks(
-                    provider_config, username, password,
-                    download_if_needed=True)
+                domain = self._login_widget.get_selected_provider()
+                self._backend.soledad_bootstrap(username, domain, password)
 
     ###################################################################
     # Service control methods: soledad
 
-    @QtCore.Slot(dict)
-    def _soledad_intermediate_stage(self, data):
-        # TODO missing param docstring
-        """
-        TRIGGERS:
-            self._soledad_bootstrapper.download_config
-
-        If there was a problem, displays it, otherwise it does nothing.
-        This is used for intermediate bootstrapping stages, in case
-        they fail.
-        """
-        passed = data[self._soledad_bootstrapper.PASSED_KEY]
-        if not passed:
-            # TODO display in the GUI:
-            # should pass signal to a slot in status_panel
-            # that sets the global status
-            logger.error("Soledad failed to start: %s" %
-                         (data[self._soledad_bootstrapper.ERROR_KEY],))
-
-    @QtCore.Slot(dict)
-    def _soledad_bootstrapped_stage(self, data):
+    @QtCore.Slot()
+    def _on_soledad_ready(self):
         """
         TRIGGERS:
-            self._soledad_bootstrapper.gen_key
-            self._soledad_bootstrapper.local_only_ready
-
-        If there was a problem, displays it, otherwise it does nothing.
-        This is used for intermediate bootstrapping stages, in case
-        they fail.
+            Signaler.soledad_bootstrap_finished
 
-        :param data: result from the bootstrapping stage for Soledad
-        :type data: dict
+        Actions to take when Soledad is ready.
         """
-        passed = data[self._soledad_bootstrapper.PASSED_KEY]
-        if not passed:
-            # TODO should actually *display* on the panel.
-            logger.debug("ERROR on soledad bootstrapping:")
-            logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY])
-            return
-
         logger.debug("Done bootstrapping Soledad")
 
-        # Update the proxy objects to point to
-        # the initialized instances.
-        setProxiedObject(self._soledad,
-                         self._soledad_bootstrapper.soledad)
-        setProxiedObject(self._keymanager,
-                         self._soledad_bootstrapper.keymanager)
-
-        # Ok, now soledad is ready, so we can allow other things that
-        # depend on soledad to start.
-        self._soledad_defer = None
-
-        # this will trigger start_imap_service
-        # and start_smtp_boostrapping
+        self._soledad_started = True
         self.soledad_ready.emit()
 
     ###################################################################
@@ -1483,19 +1411,7 @@ class MainWindow(QtGui.QMainWindow):
             return
 
         if self._provides_mx_and_enabled():
-            self._mail_conductor.start_smtp_service(self._provider_config,
-                                                    download_if_needed=True)
-
-    # XXX --- should remove from here, and connecte directly to the state
-    # machine.
-    @QtCore.Slot()
-    def _stop_smtp_service(self):
-        """
-        TRIGGERS:
-            self.logout
-        """
-        # TODO call stop_mail_service
-        self._mail_conductor.stop_smtp_service()
+            self._mail_conductor.start_smtp_service(download_if_needed=True)
 
     ###################################################################
     # Service control methods: imap
@@ -1509,69 +1425,14 @@ class MainWindow(QtGui.QMainWindow):
         # TODO in the OFFLINE mode we should also modify the  rules
         # in the mail state machine so it shows that imap is active
         # (but not smtp since it's not yet ready for offline use)
-        start_fun = self._mail_conductor.start_imap_service
-        if flags.OFFLINE is True:
-            provider_domain = self._login_widget.get_selected_provider()
-            self._provider_config.load(
-                provider.get_provider_path(provider_domain))
-        provides_mx = self._provider_config.provides_mx()
-
-        if flags.OFFLINE is True and provides_mx:
-            start_fun()
-            return
-
-        if self._provides_mx_and_enabled():
-            start_fun()
-
-    def _on_mail_client_logged_in(self, req):
-        """
-        Triggers qt signal when client login event is received.
-        """
-        self.mail_client_logged_in.emit()
-
-    @QtCore.Slot()
-    def _fetch_incoming_mail(self):
-        """
-        TRIGGERS:
-            self.mail_client_logged_in
-        """
-        # TODO connect signal directly!!!
-        self._mail_conductor.fetch_incoming_mail()
-
-    @QtCore.Slot()
-    def _stop_imap_service(self):
-        """
-        TRIGGERS:
-            self.logout
-        """
-        cv = Condition()
-        cv.acquire()
-        # TODO call stop_mail_service
-        threads.deferToThread(self._mail_conductor.stop_imap_service, cv)
-        # and wait for it to be stopped
-        logger.debug('Waiting for imap service to stop.')
-        cv.wait(self.SERVICE_STOP_TIMEOUT)
+        if self._provides_mx_and_enabled() or flags.OFFLINE:
+            self._mail_conductor.start_imap_service()
 
     # end service control methods (imap)
 
     ###################################################################
     # Service control methods: eip
 
-    def start_eip_machine(self):
-        """
-        Initializes and starts the EIP state machine
-        """
-        button = self._eip_status.eip_button
-        action = self._action_eip_startstop
-        label = self._eip_status.eip_label
-        builder = statemachines.ConnectionMachineBuilder(self._eip_connection)
-        eip_machine = builder.make_machine(button=button,
-                                           action=action,
-                                           label=label)
-        self.eip_machine = eip_machine
-        self.eip_machine.start()
-        logger.debug('eip machine started')
-
     @QtCore.Slot()
     def _disable_eip_start_action(self):
         """
@@ -1585,32 +1446,32 @@ class MainWindow(QtGui.QMainWindow):
         Enables the EIP start action in the systray menu.
         """
         self._action_eip_startstop.setEnabled(True)
+        self._eip_status.enable_eip_start()
 
     @QtCore.Slot()
     def _on_eip_connection_connected(self):
         """
         TRIGGERS:
-            self._eip_status.eip_connection_connected
-
-        Emits the EIPConnection.qtsigs.connected_signal
+            self._eip_conductor.qtsigs.connected_signal
 
         This is a little workaround for connecting the vpn-connected
         signal that currently is beeing processed under status_panel.
         After the refactor to EIPConductor this should not be necessary.
         """
-        self._eip_connection.qtsigs.connected_signal.emit()
-
-        provider_config = self._get_best_provider_config()
-        domain = provider_config.get_domain()
+        domain = self._login_widget.get_selected_provider()
 
         self._eip_status.set_provider(domain)
         self._settings.set_defaultprovider(domain)
         self._already_started_eip = True
 
         # check for connectivity
+        # we might want to leave a little time here...
         self._check_name_resolution(domain)
 
     def _check_name_resolution(self, domain):
+        # FIXME this has to be moved to backend !!!
+        # Should move to netchecks module.
+        # and separate qt from reactor...
         """
         Check if we can resolve the given domain name.
 
@@ -1641,7 +1502,7 @@ class MainWindow(QtGui.QMainWindow):
                 "missing some helper files that are needed to securely use "
                 "DNS while {1} is active. To install these helper files, quit "
                 "this application and start it again."
-            ).format(domain, self._eip_name)
+            ).format(domain, self._eip_conductor.eip_name)
 
             show_err = lambda: QtGui.QMessageBox.critical(
                 self, self.tr("Connection Error"), msg)
@@ -1663,245 +1524,42 @@ class MainWindow(QtGui.QMainWindow):
         self._enabled_services = settings.get_enabled_services(
             default_provider)
 
-        loaded = self._provisional_provider_config.load(
-            provider.get_provider_path(default_provider))
-        if loaded and settings.get_autostart_eip():
-            # XXX I think we should not try to re-download config every time,
-            # it adds some delay.
-            # Maybe if it's the first run in a session,
-            # or we can try only if it fails.
-            self._maybe_start_eip()
-        elif settings.get_autostart_eip():
-            # XXX: Display a proper message to the user
-            self.eip_needs_login.emit()
-            logger.error("Unable to load %s config, cannot autostart." %
-                         (default_provider,))
-
-    @QtCore.Slot()
-    def _start_EIP(self):
-        """
-        Starts EIP
-        """
-        self._eip_status.eip_pre_up()
-        self.user_stopped_eip = False
-
-        # Until we set an option in the preferences window, we'll assume that
-        # by default we try to autostart. If we switch it off manually, it
-        # won't try the next time.
-        self._settings.set_autostart_eip(True)
-
-        self._backend.start_eip()
-
-    @QtCore.Slot()
-    def _on_eip_connection_aborted(self):
-        """
-        TRIGGERS:
-            Signaler.eip_connection_aborted
-        """
-        logger.error("Tried to start EIP but cannot find any "
-                     "available provider!")
-
-        eip_status_label = self.tr("Could not load {0} configuration.")
-        eip_status_label = eip_status_label.format(self._eip_name)
-        self._eip_status.set_eip_status(eip_status_label, error=True)
-
-        # signal connection_aborted to state machine:
-        qtsigs = self._eip_connection.qtsigs
-        qtsigs.connection_aborted_signal.emit()
-
-    def _on_eip_openvpn_already_running(self):
-        self._eip_status.set_eip_status(
-            self.tr("Another openvpn instance is already running, and "
-                    "could not be stopped."),
-            error=True)
-        self._set_eipstatus_off()
-
-    def _on_eip_alien_openvpn_already_running(self):
-        self._eip_status.set_eip_status(
-            self.tr("Another openvpn instance is already running, and "
-                    "could not be stopped because it was not launched by "
-                    "Bitmask. Please stop it and try again."),
-            error=True)
-        self._set_eipstatus_off()
-
-    def _on_eip_openvpn_not_found_error(self):
-        self._eip_status.set_eip_status(
-            self.tr("We could not find openvpn binary."),
-            error=True)
-        self._set_eipstatus_off()
-
-    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._eip_status.set_eip_status("VPN Launcher error.", error=True)
-        self._set_eipstatus_off()
-
-    def _on_eip_no_polkit_agent_error(self):
-        self._eip_status.set_eip_status(
-            # XXX this should change to polkit-kde where
-            # applicable.
-            self.tr("We could not find any authentication agent in your "
-                    "system.<br/>Make sure you have"
-                    "<b>polkit-gnome-authentication-agent-1</b> running and"
-                    "try again."),
-            error=True)
-        self._set_eipstatus_off()
-
-    def _on_eip_no_pkexec_error(self):
-        self._eip_status.set_eip_status(
-            self.tr("We could not find <b>pkexec</b> in your system."),
-            error=True)
-        self._set_eipstatus_off()
-
-    def _on_eip_no_tun_kext_error(self):
-        self._eip_status.set_eip_status(
-            self.tr("{0} cannot be started because the tuntap extension is "
-                    "not installed properly in your "
-                    "system.").format(self._eip_name))
-        self._set_eipstatus_off()
-
-    @QtCore.Slot()
-    def _stop_eip(self):
-        """
-        TRIGGERS:
-          self._eip_connection.qtsigs.do_disconnect_signal (via state machine)
-
-        Stops vpn process and makes gui adjustments to reflect
-        the change of state.
-
-        :param abnormal: whether this was an abnormal termination.
-        :type abnormal: bool
-        """
-        self.user_stopped_eip = True
-        self._backend.stop_eip()
-
-        self._set_eipstatus_off(False)
-        self._already_started_eip = False
-
-        logger.debug('Setting autostart to: False')
-        self._settings.set_autostart_eip(False)
-
-        if self._logged_user:
-            self._eip_status.set_provider(
-                make_address(
-                    self._logged_user,
-                    self._get_best_provider_config().get_domain()))
-        self._eip_status.eip_stopped()
-
-    @QtCore.Slot()
-    def _on_eip_network_unreachable(self):
-        # XXX Should move to EIP Conductor
-        """
-        TRIGGERS:
-            self._eip_connection.qtsigs.network_unreachable
-
-        Displays a "network unreachable" error in the EIP status panel.
-        """
-        self._eip_status.set_eip_status(self.tr("Network is unreachable"),
-                                        error=True)
-        self._eip_status.set_eip_status_icon("error")
-
-    @QtCore.Slot()
-    def _do_eip_restart(self):
-        # XXX Should move to EIP Conductor
-        """
-        TRIGGERS:
-            self._eip_connection.qtsigs.process_restart
-
-        Restart the connection.
-        """
-        # for some reason, emitting the do_disconnect/do_connect
-        # signals hangs the UI.
-        self._stop_eip()
-        QtCore.QTimer.singleShot(2000, self._start_EIP)
-
-    def _set_eipstatus_off(self, error=True):
-        """
-        Sets eip status to off
-        """
-        # XXX this should be handled by the state machine.
-        self._eip_status.set_eip_status("", error=error)
-        self._eip_status.set_eip_status_icon("error")
-
-    @QtCore.Slot(int)
-    def _eip_finished(self, exitCode):
-        """
-        TRIGGERS:
-            Signaler.eip_process_finished
-
-        Triggered when the EIP/VPN process finishes to set the UI
-        accordingly.
-
-        Ideally we would have the right exit code here,
-        but the use of different wrappers (pkexec, cocoasudo) swallows
-        the openvpn exit code so we get zero exit in some cases  where we
-        shouldn't. As a workaround we just use a flag to indicate
-        a purposeful switch off, and mark everything else as unexpected.
-
-        In the near future we should trigger a native notification from here,
-        since the user really really wants to know she is unprotected asap.
-        And the right thing to do will be to fail-close.
-
-        :param exitCode: the exit code of the eip process.
-        :type exitCode: int
-        """
-        # TODO move to EIPConductor.
-        # TODO Add error catching to the openvpn log observer
-        # so we can have a more precise idea of which type
-        # of error did we have (server side, local problem, etc)
-
-        logger.info("VPN process finished with exitCode %s..."
-                    % (exitCode,))
-
-        qtsigs = self._eip_connection.qtsigs
-        signal = qtsigs.disconnected_signal
-
-        # XXX check if these exitCodes are pkexec/cocoasudo specific
-        if exitCode in (126, 127):
-            eip_status_label = self.tr(
-                "{0} could not be launched "
-                "because you did not authenticate properly.")
-            eip_status_label = eip_status_label.format(self._eip_name)
-            self._eip_status.set_eip_status(eip_status_label, error=True)
-            signal = qtsigs.connection_aborted_signal
-            self._backend.terminate_eip()
-
-        elif exitCode != 0 or not self.user_stopped_eip:
-            eip_status_label = self.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)
-            signal = qtsigs.connection_died_signal
-
-        if exitCode == 0 and IS_MAC:
-            # XXX remove this warning after I fix cocoasudo.
-            logger.warning("The above exit code MIGHT BE WRONG.")
-
-        # We emit signals to trigger transitions in the state machine:
-        signal.emit()
+        if settings.get_autostart_eip():
+            self._maybe_start_eip(autostart=True)
 
     # eip boostrapping, config etc...
 
-    def _maybe_start_eip(self):
+    def _maybe_start_eip(self, autostart=False):
         """
         Start the EIP bootstrapping sequence if the client is configured to
         do so.
+
+        :param autostart: we are autostarting EIP when this is True
+        :type autostart: bool
         """
-        if self._provides_eip_and_enabled() and not self._already_started_eip:
+        # during autostart we assume that the provider provides EIP
+        if autostart:
+            should_start = EIP_SERVICE in self._enabled_services
+        else:
+            should_start = self._provides_eip_and_enabled()
+
+        if should_start and not self._already_started_eip:
+            if self._eip_status.is_cold_start:
+                self._backend.tear_fw_down()
             # XXX this should be handled by the state machine.
+            self._enable_eip_start_action()
             self._eip_status.set_eip_status(
                 self.tr("Starting..."))
+            self._eip_status.eip_button.setEnabled(False)
 
             domain = self._login_widget.get_selected_provider()
-            self._backend.setup_eip(domain)
+            self._backend.eip_setup(domain)
 
             self._already_started_eip = True
             # we want to start soledad anyway after a certain timeout if eip
             # fails to come up
-            QtCore.QTimer.singleShot(
-                self.EIP_TIMEOUT,
-                self._maybe_run_soledad_setup_checks)
+            QtDelayedCall(self.EIP_START_TIMEOUT,
+                          self._maybe_run_soledad_setup_checks)
         else:
             if not self._already_started_eip:
                 if EIP_SERVICE in self._enabled_services:
@@ -1920,8 +1578,8 @@ class MainWindow(QtGui.QMainWindow):
         TRIGGERS:
             self._backend.signaler.eip_client_certificate_ready
 
-        Starts the VPN thread if the eip configuration is properly
-        loaded
+        Start the VPN thread if the eip configuration is properly
+        loaded.
         """
         passed = data[self._backend.PASSED_KEY]
 
@@ -1933,11 +1591,11 @@ class MainWindow(QtGui.QMainWindow):
             return
 
         # DO START EIP Connection!
-        self._eip_connection.qtsigs.do_connect_signal.emit()
+        self._eip_conductor.do_connect()
 
     @QtCore.Slot(dict)
     def _eip_intermediate_stage(self, data):
-        # TODO missing param
+        # TODO missing param documentation
         """
         TRIGGERS:
             self._backend.signaler.eip_config_ready
@@ -1952,33 +1610,10 @@ class MainWindow(QtGui.QMainWindow):
                 self.tr("Unable to connect: Problem with provider"))
             logger.error(data[self._backend.ERROR_KEY])
             self._already_started_eip = False
+            self._eip_status.aborted()
 
     # end of EIP methods ---------------------------------------------
 
-    def _get_best_provider_config(self):
-        """
-        Returns the best ProviderConfig to use at a moment. We may
-        have to use self._provider_config or
-        self._provisional_provider_config depending on the start
-        status.
-
-        :rtype: ProviderConfig
-        """
-        # TODO move this out of gui.
-        leap_assert(self._provider_config is not None or
-                    self._provisional_provider_config is not None,
-                    "We need a provider config")
-
-        provider_config = None
-        if self._provider_config.loaded():
-            provider_config = self._provider_config
-        elif self._provisional_provider_config.loaded():
-            provider_config = self._provisional_provider_config
-        else:
-            leap_assert(False, "We could not find any usable ProviderConfig.")
-
-        return provider_config
-
     @QtCore.Slot()
     def _logout(self):
         """
@@ -1987,16 +1622,11 @@ class MainWindow(QtGui.QMainWindow):
 
         Starts the logout sequence
         """
-        setProxiedObject(self._soledad, None)
-
         self._cancel_ongoing_defers()
 
-        # reset soledad status flag
-        self._already_started_soledad = False
-
         # XXX: If other defers are doing authenticated stuff, this
         # might conflict with those. CHECK!
-        self._backend.logout()
+        self._backend.user_logout()
         self.logout.emit()
 
     @QtCore.Slot()
@@ -2080,59 +1710,37 @@ class MainWindow(QtGui.QMainWindow):
     # cleanup and quit methods
     #
 
-    def _cleanup_pidfiles(self):
-        """
-        Removes lockfiles on a clean shutdown.
-
-        Triggered after aboutToQuit signal.
+    def _stop_services(self):
         """
-        if IS_WIN:
-            WindowsLock.release_all_locks()
-
-    def _cleanup_and_quit(self):
-        """
-        Call all the cleanup actions in a serialized way.
-        Should be called from the quit function.
+        Stop services and cancel ongoing actions (if any).
         """
-        logger.debug('About to quit, doing cleanup...')
-
-        self._stop_imap_service()
-
-        if self._logged_user is not None:
-            self._backend.logout()
+        logger.debug('About to quit, doing cleanup.')
 
-        if self._soledad_bootstrapper.soledad is not None:
-            logger.debug("Closing soledad...")
-            self._soledad_bootstrapper.soledad.close()
-        else:
-            logger.error("No instance of soledad was found.")
+        self._cancel_ongoing_defers()
 
-        logger.debug('Terminating vpn')
-        self._backend.stop_eip(shutdown=True)
+        self._services_being_stopped = {'imap', 'eip'}
 
-        # We need to give some time to the ongoing signals for shutdown
-        # to come into action. This needs to be solved using
-        # back-communication from backend.
-        QtCore.QTimer.singleShot(3000, self._shutdown)
+        imap_stopped = lambda: self._remove_service('imap')
+        self._backend.signaler.imap_stopped.connect(imap_stopped)
 
-    def _shutdown(self):
-        """
-        Actually shutdown.
-        """
-        self._cancel_ongoing_defers()
+        eip_stopped = lambda: self._remove_service('eip')
+        self._backend.signaler.eip_stopped.connect(eip_stopped)
 
-        # TODO missing any more cancels?
+        logger.debug('Stopping mail services')
+        self._backend.imap_stop_service()
+        self._backend.smtp_stop_service()
 
-        logger.debug('Cleaning pidfiles')
-        self._cleanup_pidfiles()
-        if self._quit_callback:
-            self._quit_callback()
+        if self._logged_user is not None:
+            logger.debug("Doing logout")
+            self._backend.user_logout()
 
-        logger.debug('Bye.')
+        logger.debug('Terminating vpn')
+        self._backend.eip_stop(shutdown=True)
 
     def quit(self):
         """
-        Cleanup and tidely close the main window before quitting.
+        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.
@@ -2142,25 +1750,72 @@ class MainWindow(QtGui.QMainWindow):
         if self._systray is not None:
             self._systray.showMessage(
                 self.tr('Quitting...'),
-                self.tr('The app is quitting, please wait.'))
+                self.tr('Bitmask is quitting, please wait.'))
 
         # explicitly process events to display tooltip immediately
-        QtCore.QCoreApplication.processEvents()
+        QtCore.QCoreApplication.processEvents(0, 10)
+
+        # Close other windows if any.
+        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._cleanup_and_quit()
+        self._stop_services()
 
-        # We queue the call to stop since we need to wait until EIP is stopped.
-        # Otherwise we may exit leaving an unmanaged openvpn process.
-        reactor.callLater(0, self._backend.stop)
         self._really_quit = True
 
-        if self._wizard:
-            self._wizard.close()
+        # call final quit when all the services are stopped
+        self.all_services_stopped.connect(self.final_quit)
+        # or if we reach the timeout
+        self._quit_timeout_callater = reactor.callLater(
+            self.SERVICES_STOP_TIMEOUT, self.final_quit)
 
-        if self._logger_window:
-            self._logger_window.close()
+    @QtCore.Slot()
+    def _remove_service(self, service):
+        """
+        Remove the given service from the waiting list and check if we have
+        running services that we need to wait until we quit.
+        Emit self.all_services_stopped signal if we don't need to keep waiting.
 
+        :param service: the service that we want to remove
+        :type service: str
+        """
+        self._services_being_stopped.discard(service)
+
+        if not self._services_being_stopped:
+            logger.debug("All services stopped.")
+            self.all_services_stopped.emit()
+
+    @QtCore.Slot()
+    def final_quit(self):
+        """
+        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...')
+
+        try:
+            # disconnect signal if we get here due a timeout.
+            self.all_services_stopped.disconnect(self.final_quit)
+        except RuntimeError:
+            pass  # Signal was not connected
+
+        # Cancel timeout to avoid being called if we reached here through the
+        # signal
+        if self._quit_timeout_callater.active():
+            self._quit_timeout_callater.cancel()
+
+        # Remove lockfiles on a clean shutdown.
+        logger.debug('Cleaning pidfiles')
+        if IS_WIN:
+            WindowsLock.release_all_locks()
+
+        self._backend.stop()
         self.close()
+
+        reactor.callLater(1, self._quit_callback)
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 2947c5db11e6121a7c47e553e3b3907d2b8ba0aa..a3b81d38d1f90c20b071b0ac67266bf45c336a4e 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -23,15 +23,10 @@ import logging
 from functools import partial
 
 from PySide import QtCore, QtGui
-from zope.proxy import sameProxiedObjects
 
-from leap.bitmask.provider import get_provider_path
 from leap.bitmask.config.leapsettings import LeapSettings
 from leap.bitmask.gui.ui_preferences import Ui_Preferences
-from leap.soledad.client import NoStorageSecret
-from leap.bitmask.util.password import basic_password_checks
-from leap.bitmask.services import get_supported
-from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.util.credentials import password_checks
 from leap.bitmask.services import get_service_display_name, MX_SERVICE
 
 logger = logging.getLogger(__name__)
@@ -43,32 +38,31 @@ class PreferencesWindow(QtGui.QDialog):
     """
     preferences_saved = QtCore.Signal()
 
-    def __init__(self, parent, backend, provider_config,
-                 soledad, username, domain):
+    def __init__(self, parent, username, domain, backend, soledad_started, mx):
         """
         :param parent: parent object of the PreferencesWindow.
         :parent type: QWidget
-        :param backend: Backend being used
-        :type backend: Backend
-        :param provider_config: ProviderConfig object.
-        :type provider_config: ProviderConfig
-        :param soledad: Soledad instance
-        :type soledad: Soledad
         :param username: the user set in the login widget
         :type username: unicode
         :param domain: the selected domain in the login widget
         :type domain: unicode
+        :param backend: Backend being used
+        :type backend: Backend
+        :param soledad_started: whether soledad has started or not
+        :type soledad_started: bool
+        :param mx: whether the current provider provides mx or not.
+        :type mx: bool
         """
         QtGui.QDialog.__init__(self, parent)
         self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
 
-        self._backend = backend
-        self._settings = LeapSettings()
-        self._soledad = soledad
-        self._provider_config = provider_config
         self._username = username
         self._domain = domain
+        self._backend = backend
+        self._soledad_started = soledad_started
+        self._mx_provided = mx
 
+        self._settings = LeapSettings()
         self._backend_connect()
 
         # Load UI
@@ -89,50 +83,17 @@ class PreferencesWindow(QtGui.QDialog):
         else:
             self._add_configured_providers()
 
-        self._backend.get_logged_in_status()
+        if self._username is None:
+            self._not_logged_in()
+        else:
+            self.ui.gbPasswordChange.setEnabled(True)
+            if self._mx_provided:
+                self._provides_mx()
 
         self._select_provider_by_name(domain)
 
-    @QtCore.Slot()
-    def _is_logged_in(self):
-        """
-        TRIGGERS:
-            Signaler.srp_status_logged_in
-
-        Actions to perform is the user is logged in.
-        """
-        settings = self._settings
-        pw_enabled = True
-
-        # check if provider has 'mx' ...
-        # TODO: we should move this to the backend.
-        if self._provider_config.provides_mx():
-            enabled_services = settings.get_enabled_services(self._domain)
-            mx_name = get_service_display_name(MX_SERVICE)
-
-            # ... and if the user have it enabled
-            if MX_SERVICE not in enabled_services:
-                msg = self.tr("You need to enable {0} in order to change "
-                              "the password.".format(mx_name))
-                self._set_password_change_status(msg, error=True)
-                pw_enabled = False
-            else:
-                # check if Soledad is bootstrapped
-                if sameProxiedObjects(self._soledad, None):
-                    msg = self.tr(
-                        "You need to wait until {0} is ready in "
-                        "order to change the password.".format(mx_name))
-                    self._set_password_change_status(msg)
-                    pw_enabled = False
-
-        self.ui.gbPasswordChange.setEnabled(pw_enabled)
-
-    @QtCore.Slot()
     def _not_logged_in(self):
         """
-        TRIGGERS:
-            Signaler.srp_status_not_logged_in
-
         Actions to perform if the user is not logged in.
         """
         msg = self.tr(
@@ -140,6 +101,30 @@ class PreferencesWindow(QtGui.QDialog):
         self._set_password_change_status(msg)
         self.ui.gbPasswordChange.setEnabled(False)
 
+    def _provides_mx(self):
+        """
+        Actions to perform if the provider provides MX.
+        """
+        pw_enabled = True
+        enabled_services = self._settings.get_enabled_services(self._domain)
+        mx_name = get_service_display_name(MX_SERVICE)
+
+        if MX_SERVICE not in enabled_services:
+            msg = self.tr("You need to enable {0} in order to change "
+                          "the password.".format(mx_name))
+            self._set_password_change_status(msg, error=True)
+            pw_enabled = False
+        else:
+            # check if Soledad is bootstrapped
+            if not self._soledad_started:
+                msg = self.tr(
+                    "You need to wait until {0} is ready in "
+                    "order to change the password.".format(mx_name))
+                self._set_password_change_status(msg)
+                pw_enabled = False
+
+        self.ui.gbPasswordChange.setEnabled(pw_enabled)
+
     @QtCore.Slot()
     def set_soledad_ready(self):
         """
@@ -200,7 +185,7 @@ class PreferencesWindow(QtGui.QDialog):
         new_password = self.ui.leNewPassword.text()
         new_password2 = self.ui.leNewPassword2.text()
 
-        ok, msg = basic_password_checks(username, new_password, new_password2)
+        ok, msg = password_checks(username, new_password, new_password2)
 
         if not ok:
             self._set_changing_password(False)
@@ -209,10 +194,10 @@ class PreferencesWindow(QtGui.QDialog):
             return
 
         self._set_changing_password(True)
-        self._backend.change_password(current_password, new_password)
+        self._backend.user_change_password(current_password, new_password)
 
     @QtCore.Slot()
-    def _change_password_ok(self):
+    def _srp_change_password_ok(self):
         """
         TRIGGERS:
             self._backend.signaler.srp_password_change_ok
@@ -221,12 +206,44 @@ class PreferencesWindow(QtGui.QDialog):
         """
         new_password = self.ui.leNewPassword.text()
         logger.debug("SRP password changed successfully.")
-        try:
-            self._soledad.change_passphrase(new_password)
-            logger.debug("Soledad password changed successfully.")
-        except NoStorageSecret:
-            logger.debug(
-                "No storage secret for password change in Soledad.")
+
+        if self._mx_provided:
+            self._backend.soledad_change_password(new_password)
+        else:
+            self._change_password_success()
+
+    @QtCore.Slot(unicode)
+    def _srp_change_password_problem(self, msg):
+        """
+        TRIGGERS:
+            self._backend.signaler.srp_password_change_error
+            self._backend.signaler.srp_password_change_badpw
+
+        Callback used to display an error on changing password.
+
+        :param msg: the message to show to the user.
+        :type msg: unicode
+        """
+        logger.error("Error changing password")
+        self._set_password_change_status(msg, error=True)
+        self._set_changing_password(False)
+
+    @QtCore.Slot()
+    def _soledad_change_password_ok(self):
+        """
+        TRIGGERS:
+            Signaler.soledad_password_change_ok
+
+        Soledad password change went OK.
+        """
+        logger.debug("Soledad password changed successfully.")
+        self._change_password_success()
+
+    def _change_password_success(self):
+        """
+        Callback used to display a successfully changed password.
+        """
+        logger.debug("Soledad password changed successfully.")
 
         self._set_password_change_status(
             self.tr("Password changed successfully."), success=True)
@@ -234,18 +251,17 @@ class PreferencesWindow(QtGui.QDialog):
         self._set_changing_password(False)
 
     @QtCore.Slot(unicode)
-    def _change_password_problem(self, msg):
+    def _soledad_change_password_problem(self, msg):
         """
         TRIGGERS:
-            self._backend.signaler.srp_password_change_error
-            self._backend.signaler.srp_password_change_badpw
+            Signaler.soledad_password_change_error
 
         Callback used to display an error on changing password.
 
         :param msg: the message to show to the user.
         :type msg: unicode
         """
-        logger.error("Error changing password")
+        logger.error("Error changing soledad password")
         self._set_password_change_status(msg, error=True)
         self._set_changing_password(False)
 
@@ -321,8 +337,7 @@ class PreferencesWindow(QtGui.QDialog):
         TRIGGERS:
             self.ui.cbProvidersServices.currentIndexChanged[unicode]
 
-        Loads the services that the provider provides into the UI for
-        the user to enable or disable.
+        Fill the services list with the selected provider's services.
 
         :param domain: the domain of the provider to load services from.
         :type domain: str
@@ -333,10 +348,6 @@ class PreferencesWindow(QtGui.QDialog):
         if not domain:
             return
 
-        provider_config = self._get_provider_config(domain)
-        if provider_config is None:
-            return
-
         # set the proper connection for the 'save' button
         try:
             self.ui.pbSaveServices.clicked.disconnect()
@@ -346,7 +357,21 @@ class PreferencesWindow(QtGui.QDialog):
         save_services = partial(self._save_enabled_services, domain)
         self.ui.pbSaveServices.clicked.connect(save_services)
 
-        services = get_supported(provider_config.get_services())
+        self._backend.provider_get_supported_services(domain)
+
+    @QtCore.Slot(str)
+    def _load_services(self, services):
+        """
+        TRIGGERS:
+            self.ui.cbProvidersServices.currentIndexChanged[unicode]
+
+        Loads the services that the provider provides into the UI for
+        the user to enable or disable.
+
+        :param domain: the domain of the provider to load services from.
+        :type domain: str
+        """
+        domain = self.ui.cbProvidersServices.currentText()
         services_conf = self._settings.get_enabled_services(domain)
 
         # discard changes if other provider is selected
@@ -394,36 +419,26 @@ class PreferencesWindow(QtGui.QDialog):
         self._set_providers_services_status(msg, success=True)
         self.preferences_saved.emit()
 
-    def _get_provider_config(self, domain):
-        """
-        Helper to return a valid Provider Config from the domain name.
-
-        :param domain: the domain name of the provider.
-        :type domain: str
-
-        :rtype: ProviderConfig or None if there is a problem loading the config
-        """
-        provider_config = ProviderConfig()
-        if not provider_config.load(get_provider_path(domain)):
-            provider_config = None
-
-        return provider_config
-
     def _backend_connect(self):
         """
         Helper to connect to backend signals
         """
         sig = self._backend.signaler
 
-        sig.srp_status_logged_in.connect(self._is_logged_in)
-        sig.srp_status_not_logged_in.connect(self._not_logged_in)
+        sig.prov_get_supported_services.connect(self._load_services)
 
-        sig.srp_password_change_ok.connect(self._change_password_ok)
+        sig.srp_password_change_ok.connect(self._srp_change_password_ok)
 
-        pwd_change_error = lambda: self._change_password_problem(
+        pwd_change_error = lambda: self._srp_change_password_problem(
             self.tr("There was a problem changing the password."))
         sig.srp_password_change_error.connect(pwd_change_error)
 
-        pwd_change_badpw = lambda: self._change_password_problem(
+        pwd_change_badpw = lambda: self._srp_change_password_problem(
             self.tr("You did not enter a correct current password."))
         sig.srp_password_change_badpw.connect(pwd_change_badpw)
+
+        sig.soledad_password_change_ok.connect(
+            self._soledad_change_password_ok)
+
+        sig.soledad_password_change_error.connect(
+            self._soledad_change_password_problem)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 31938a700f2465480bac66f496115edc7a82ab12..00a1387eed1a934696918f7b9abb3fae464e9947 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -504,6 +504,11 @@ class ConnectionMachineBuilder(object):
             conn.qtsigs.connection_died_signal,
             states[_OFF])
 
+        # XXX adding this---------------------
+        states[_ON].addTransition(
+            conn.qtsigs.do_disconnect_signal,
+            states[_DIS])
+
         # * If we receive the connection_aborted, we transition
         #   from connecting to the off state
         states[_CON].addTransition(
@@ -551,7 +556,8 @@ class ConnectionMachineBuilder(object):
         # TODO add tooltip
 
         # OFF State ----------------------
-        off = QState()
+        off = SignallingState(
+            None, name=conn.name)
         off_label = _tr("Turn {0}").format(
             conn.Connected.short_label)
         if button:
@@ -559,11 +565,15 @@ class ConnectionMachineBuilder(object):
                 button, 'text', off_label)
             off.assignProperty(
                 button, 'enabled', True)
+            off.assignProperty(
+                button, 'visible', True)
         if action:
             off.assignProperty(
                 action, 'text', off_label)
             off.assignProperty(
                 action, 'enabled', True)
+            off.assignProperty(
+                action, 'visible', True)
         off.setObjectName(_OFF)
         states[_OFF] = off
 
@@ -587,7 +597,10 @@ class ConnectionMachineBuilder(object):
         states[_CON] = connecting
 
         # ON State ------------------------
-        on = QState()
+        on = SignallingState(
+            None, name=conn.name)
+        on_label = _tr("Turn {0}").format(
+            conn.Disconnected.short_label)
         if button:
             on.assignProperty(
                 button, 'text', on_label)
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index 64821ad6ab78ff6c11ca3173142429a1a0407f92..7216bb0afddaddfb25a23f00602ac19cf221350f 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -28,7 +28,7 @@
      <property name="verticalSpacing">
       <number>0</number>
      </property>
-     <item row="0" column="2">
+     <item row="0" column="4">
       <widget class="QPushButton" name="btnEipStartStop">
        <property name="text">
         <string>Turn On</string>
@@ -51,7 +51,7 @@
        </property>
       </widget>
      </item>
-     <item row="3" column="1">
+     <item row="3" column="2">
       <widget class="QLabel" name="lblEIPStatus">
        <property name="maximumSize">
         <size>
@@ -70,7 +70,7 @@
        </property>
       </widget>
      </item>
-     <item row="0" column="1">
+     <item row="0" column="2">
       <widget class="QLabel" name="lblEIPMessage">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
@@ -86,7 +86,7 @@
        </property>
       </widget>
      </item>
-     <item row="0" column="3">
+     <item row="0" column="5">
       <widget class="QLabel" name="lblVPNStatusIcon">
        <property name="maximumSize">
         <size>
@@ -105,7 +105,7 @@
        </property>
       </widget>
      </item>
-     <item row="1" column="1">
+     <item row="1" column="2">
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
@@ -118,7 +118,7 @@
        </property>
       </spacer>
      </item>
-     <item row="2" column="1" colspan="3">
+     <item row="2" column="2" colspan="4">
       <widget class="QWidget" name="eip_bandwidth" native="true">
        <layout class="QHBoxLayout" name="horizontalLayout">
         <property name="spacing">
@@ -161,12 +161,13 @@
             <property name="text">
              <string>0.0 KB/s</string>
             </property>
+            <property name="icon">
+             <iconset resource="../../../../../data/resources/mainwindow.qrc">
+              <normaloff>:/images/black/32/arrow-down.png</normaloff>:/images/black/32/arrow-down.png</iconset>
+            </property>
             <property name="flat">
              <bool>true</bool>
             </property>
-            <property name="icon">
-             <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
-            </property>
            </widget>
           </item>
           <item>
@@ -211,12 +212,13 @@
             <property name="text">
              <string>0.0 KB/s</string>
             </property>
+            <property name="icon">
+             <iconset resource="../../../../../data/resources/mainwindow.qrc">
+              <normaloff>:/images/black/32/arrow-up.png</normaloff>:/images/black/32/arrow-up.png</iconset>
+            </property>
             <property name="flat">
              <bool>true</bool>
             </property>
-            <property name="icon">
-             <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
-            </property>
            </widget>
           </item>
           <item>
@@ -237,6 +239,20 @@
        </layout>
       </widget>
      </item>
+     <item row="0" column="3">
+      <widget class="QPushButton" name="btnFwDown">
+       <property name="text">
+        <string>Turn Off</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLabel" name="lblGatewayCountryCode">
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
   </layout>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 020a58e24701ece1ece8c88a964e3e0d85f2379b..4d7749072d123c4a85275288173ade04f656e2ce 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -26,11 +26,10 @@ from PySide import QtCore, QtGui
 
 from leap.bitmask.config import flags
 from leap.bitmask.config.leapsettings import LeapSettings
-from leap.bitmask.config.providerconfig import ProviderConfig
-from leap.bitmask.provider import get_provider_path
 from leap.bitmask.services import get_service_display_name, get_supported
+from leap.bitmask.util.credentials import password_checks, username_checks
+from leap.bitmask.util.credentials import USERNAME_REGEX
 from leap.bitmask.util.keyring_helpers import has_keyring
-from leap.bitmask.util.password import basic_password_checks
 
 from ui_wizard import Ui_Wizard
 
@@ -49,8 +48,6 @@ class Wizard(QtGui.QWizard):
     REGISTER_USER_PAGE = 4
     SERVICES_PAGE = 5
 
-    BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
-
     def __init__(self, backend, bypass_checks=False):
         """
         Constructor for the main Wizard.
@@ -89,10 +86,9 @@ class Wizard(QtGui.QWizard):
         self._backend_connect()
 
         self._domain = None
-        # HACK!! We need provider_config for the time being, it'll be
-        # removed
-        self._provider_config = (
-            self._backend._components["provider"]._provider_config)
+
+        # this details are set when the provider download is complete.
+        self._provider_details = None
 
         # We will store a reference to the defers for eventual use
         # (eg, to cancel them) but not doing anything with them right now.
@@ -118,7 +114,7 @@ class Wizard(QtGui.QWizard):
 
         self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks)
 
-        usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+        usernameRe = QtCore.QRegExp(USERNAME_REGEX)
         self.ui.lblUser.setValidator(
             QtGui.QRegExpValidator(usernameRe, self))
 
@@ -231,6 +227,12 @@ class Wizard(QtGui.QWizard):
         if reset:
             self._reset_provider_check()
 
+    def _focus_username(self):
+        """
+        Focus at the username lineedit for the registration page
+        """
+        self.ui.lblUser.setFocus()
+
     def _focus_password(self):
         """
         Focuses at the password lineedit for the registration page
@@ -253,16 +255,22 @@ class Wizard(QtGui.QWizard):
         password = self.ui.lblPassword.text()
         password2 = self.ui.lblPassword2.text()
 
-        ok, msg = basic_password_checks(username, password, password2)
-        if ok:
+        user_ok, msg = username_checks(username)
+        if user_ok:
+            pass_ok, msg = password_checks(username, password, password2)
+
+        if user_ok and pass_ok:
             self._set_register_status(self.tr("Starting registration..."))
 
-            self._backend.register_user(self._domain, username, password)
+            self._backend.user_register(self._domain, username, password)
             self._username = username
             self._password = password
         else:
+            if user_ok:
+                self._focus_password()
+            else:
+                self._focus_username()
             self._set_register_status(msg, error=True)
-            self._focus_password()
             self.ui.btnRegister.setEnabled(True)
 
     def _set_registration_fields_visibility(self, visible):
@@ -406,7 +414,7 @@ class Wizard(QtGui.QWizard):
 
         self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
         self._provider_select_defer = self._backend.\
-            setup_provider(self._domain)
+            provider_setup(self._domain)
 
     @QtCore.Slot(bool)
     def _skip_provider_checks(self, skip):
@@ -502,10 +510,12 @@ class Wizard(QtGui.QWizard):
         check. Since this check is the last of this set, it also
         completes the page if passed
         """
-        if self._provider_config.load(get_provider_path(self._domain)):
+        if data[self._backend.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)
         else:
             new_data = {
                 self._backend.PASSED_KEY: False,
@@ -527,6 +537,16 @@ class Wizard(QtGui.QWizard):
         else:
             self.ui.cbProviders.setEnabled(True)
 
+    @QtCore.Slot()
+    def _provider_get_details(self, details):
+        """
+        Set the details for the just downloaded provider.
+
+        :param details: the details of the provider.
+        :type details: ProviderConfigLight
+        """
+        self._provider_details = details
+
     @QtCore.Slot(dict)
     def _download_ca_cert(self, data):
         """
@@ -594,11 +614,9 @@ class Wizard(QtGui.QWizard):
         the user to enable or disable.
         """
         self.ui.grpServices.setTitle(
-            self.tr("Services by %s") %
-            (self._provider_config.get_name(),))
+            self.tr("Services by {0}").format(self._provider_details.name))
 
-        services = get_supported(
-            self._provider_config.get_services())
+        services = get_supported(self._provider_details.services)
 
         for service in services:
             try:
@@ -641,38 +659,31 @@ class Wizard(QtGui.QWizard):
             if not self._provider_setup_ok:
                 self._reset_provider_setup()
                 sub_title = self.tr("Gathering configuration options for {0}")
-                sub_title = sub_title.format(self._provider_config.get_name())
+                sub_title = sub_title.format(self._provider_details.name)
                 self.page(pageId).setSubTitle(sub_title)
                 self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
                 self._provider_setup_defer = self._backend.\
                     provider_bootstrap(self._domain)
 
         if pageId == self.PRESENT_PROVIDER_PAGE:
-            self.page(pageId).setSubTitle(self.tr("Description of services "
-                                                  "offered by %s") %
-                                          (self._provider_config
-                                           .get_name(),))
-
-            lang = QtCore.QLocale.system().name()
-            self.ui.lblProviderName.setText(
-                "<b>%s</b>" %
-                (self._provider_config.get_name(lang=lang),))
-            self.ui.lblProviderURL.setText(
-                "https://%s" % (self._provider_config.get_domain(),))
-            self.ui.lblProviderDesc.setText(
-                "<i>%s</i>" %
-                (self._provider_config.get_description(lang=lang),))
-
-            self.ui.lblServicesOffered.setText(self._provider_config
-                                               .get_services_string())
-            self.ui.lblProviderPolicy.setText(self._provider_config
-                                              .get_enrollment_policy())
+            sub_title = self.tr("Description of services offered by {0}")
+            sub_title = sub_title.format(self._provider_details.name)
+            self.page(pageId).setSubTitle(sub_title)
+
+            details = self._provider_details
+            name = "<b>{0}</b>".format(details.name)
+            domain = "https://{0}".format(details.domain)
+            description = "<i>{0}</i>".format(details.description)
+            self.ui.lblProviderName.setText(name)
+            self.ui.lblProviderURL.setText(domain)
+            self.ui.lblProviderDesc.setText(description)
+            self.ui.lblServicesOffered.setText(details.services_string)
+            self.ui.lblProviderPolicy.setText(details.enrollment_policy)
 
         if pageId == self.REGISTER_USER_PAGE:
-            self.page(pageId).setSubTitle(self.tr("Register a new user with "
-                                                  "%s") %
-                                          (self._provider_config
-                                           .get_name(),))
+            sub_title = self.tr("Register a new user with {0}")
+            sub_title = sub_title.format(self._provider_details.name)
+            self.page(pageId).setSubTitle(sub_title)
             self.ui.chkRemember.setVisible(False)
 
         if pageId == self.SERVICES_PAGE:
@@ -695,8 +706,6 @@ class Wizard(QtGui.QWizard):
         if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE):
             if self._use_existing_provider:
                 self._domain = self.ui.cbProviders.currentText()
-                self._provider_config = ProviderConfig.get_provider_config(
-                    self._domain)
                 if self._show_register:
                     return self.REGISTER_USER_PAGE
                 else:
@@ -721,6 +730,7 @@ class Wizard(QtGui.QWizard):
         sig.prov_name_resolution.connect(self._name_resolution)
         sig.prov_https_connection.connect(self._https_connection)
         sig.prov_download_provider_info.connect(self._download_provider_info)
+        sig.prov_get_details.connect(self._provider_get_details)
 
         sig.prov_download_ca_cert.connect(self._download_ca_cert)
         sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint)
diff --git a/src/leap/bitmask/logs/__init__.py b/src/leap/bitmask/logs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0516b3040d19f4a2cb877d9339f7304adee3af3c
--- /dev/null
+++ b/src/leap/bitmask/logs/__init__.py
@@ -0,0 +1,3 @@
+# levelname length == 8, since 'CRITICAL' is the longest
+LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - '
+              'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s')
diff --git a/src/leap/bitmask/util/leap_log_handler.py b/src/leap/bitmask/logs/leap_log_handler.py
similarity index 99%
rename from src/leap/bitmask/util/leap_log_handler.py
rename to src/leap/bitmask/logs/leap_log_handler.py
index 807e53d46613092baea925e0c4ee8e807635d241..24141638f6bf53dd5b9ce6a3e7bf38ce9ea95798 100644
--- a/src/leap/bitmask/util/leap_log_handler.py
+++ b/src/leap/bitmask/logs/leap_log_handler.py
@@ -21,7 +21,7 @@ import logging
 
 from PySide import QtCore
 
-from leap.bitmask.util import LOG_FORMAT
+from leap.bitmask.logs import LOG_FORMAT
 
 
 class LogHandler(logging.Handler):
diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/logs/log_silencer.py
similarity index 100%
rename from src/leap/bitmask/util/log_silencer.py
rename to src/leap/bitmask/logs/log_silencer.py
diff --git a/src/leap/bitmask/util/streamtologger.py b/src/leap/bitmask/logs/streamtologger.py
similarity index 100%
rename from src/leap/bitmask/util/streamtologger.py
rename to src/leap/bitmask/logs/streamtologger.py
diff --git a/src/leap/bitmask/util/tests/test_leap_log_handler.py b/src/leap/bitmask/logs/tests/test_leap_log_handler.py
similarity index 98%
rename from src/leap/bitmask/util/tests/test_leap_log_handler.py
rename to src/leap/bitmask/logs/tests/test_leap_log_handler.py
index 518fd35bbc3cc4fd8c1a7437e3ffc66ad74223f8..20b09aefcd2f9577417322d003aa963068cb15a3 100644
--- a/src/leap/bitmask/util/tests/test_leap_log_handler.py
+++ b/src/leap/bitmask/logs/tests/test_leap_log_handler.py
@@ -24,7 +24,7 @@ except ImportError:
 
 import logging
 
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
 from leap.bitmask.util.pyside_tests_helper import BasicPySlotCase
 from leap.common.testing.basetest import BaseLeapTest
 
diff --git a/src/leap/bitmask/util/tests/test_streamtologger.py b/src/leap/bitmask/logs/tests/test_streamtologger.py
similarity index 98%
rename from src/leap/bitmask/util/tests/test_streamtologger.py
rename to src/leap/bitmask/logs/tests/test_streamtologger.py
index fc97b79483e0263540b56671256d89a229940905..9bbadde8705238bd07ef4b9e4e803a8a9099bbe9 100644
--- a/src/leap/bitmask/util/tests/test_streamtologger.py
+++ b/src/leap/bitmask/logs/tests/test_streamtologger.py
@@ -26,7 +26,7 @@ except ImportError:
 import logging
 import sys
 
-from leap.bitmask.util.streamtologger import StreamToLogger
+from leap.bitmask.logs.streamtologger import StreamToLogger
 from leap.common.testing.basetest import BaseLeapTest
 
 
diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..06959c454afdf992c421d4a3d5c3aaaedcae003d
--- /dev/null
+++ b/src/leap/bitmask/logs/utils.py
@@ -0,0 +1,92 @@
+import logging
+import sys
+
+from leap.bitmask.logs import LOG_FORMAT
+from leap.bitmask.logs.log_silencer import SelectiveSilencerFilter
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.streamtologger import StreamToLogger
+from leap.bitmask.platform_init import IS_WIN
+
+
+def get_logger(debug=False, logfile=None, replace_stdout=True):
+    """
+    Create the logger and attach the handlers.
+
+    :param debug: the level of the messages that we should log
+    :type debug: bool
+    :param logfile: the file name of where we should to save the logs
+    :type logfile: str
+    :return: the new logger with the attached handlers.
+    :rtype: logging.Logger
+    """
+    # TODO: get severity from command line args
+    if debug:
+        level = logging.DEBUG
+    else:
+        level = logging.WARNING
+
+    # Create logger and formatter
+    logger = logging.getLogger(name='leap')
+    logger.setLevel(level)
+    formatter = logging.Formatter(LOG_FORMAT)
+
+    # Console handler
+    try:
+        import coloredlogs
+        console = coloredlogs.ColoredStreamHandler(level=level)
+    except ImportError:
+        console = logging.StreamHandler()
+        console.setLevel(level)
+        console.setFormatter(formatter)
+        using_coloredlog = False
+    else:
+        using_coloredlog = True
+
+    if using_coloredlog:
+        replace_stdout = False
+
+    silencer = SelectiveSilencerFilter()
+    console.addFilter(silencer)
+    logger.addHandler(console)
+    logger.debug('Console handler plugged!')
+
+    # LEAP custom handler
+    leap_handler = LeapLogHandler()
+    leap_handler.setLevel(level)
+    leap_handler.addFilter(silencer)
+    logger.addHandler(leap_handler)
+    logger.debug('Leap handler plugged!')
+
+    # File handler
+    if logfile is not None:
+        logger.debug('Setting logfile to %s ', logfile)
+        fileh = logging.FileHandler(logfile)
+        fileh.setLevel(logging.DEBUG)
+        fileh.setFormatter(formatter)
+        fileh.addFilter(silencer)
+        logger.addHandler(fileh)
+        logger.debug('File handler plugged!')
+
+    if replace_stdout:
+        replace_stdout_stderr_with_logging(logger)
+
+    return logger
+
+
+def replace_stdout_stderr_with_logging(logger):
+    """
+    Replace:
+        - the standard output
+        - the standard error
+        - the twisted log output
+    with a custom one that writes to the logger.
+    """
+    # Disabling this on windows since it breaks ALL THE THINGS
+    # The issue for this is #4149
+    if not IS_WIN:
+        sys.stdout = StreamToLogger(logger, logging.DEBUG)
+        sys.stderr = StreamToLogger(logger, logging.ERROR)
+
+        # Replace twisted's logger to use our custom output.
+        from twisted.python import log
+        log.startLogging(sys.stdout)
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index f2710c581a5a2646efbe2b228c66de953808e32d..b282a229afe59c09c7f5c5645dbdfd6ab9792e25 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -14,15 +14,14 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
 """
-Platform dependant initializing code
+Platform-dependant initialization code.
 """
-
 import logging
 import os
 import platform
 import stat
+import sys
 import subprocess
 import tempfile
 
@@ -33,7 +32,6 @@ from leap.bitmask.services.eip import get_vpn_launcher
 from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher
 from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher
 from leap.bitmask.util import first
-from leap.bitmask.util import privilege_policies
 
 
 logger = logging.getLogger(__name__)
@@ -48,7 +46,7 @@ _system = platform.system()
 
 def init_platform():
     """
-    Returns the right initializer for the platform we are running in, or
+    Return the right initializer for the platform we are running in, or
     None if no proper initializer is found
     """
     initializer = None
@@ -80,7 +78,7 @@ UPDOWN_BADEXEC_MSG = BADEXEC_MSG % (
 
 def get_missing_updown_dialog():
     """
-    Creates a dialog for notifying of missing updown scripts.
+    Create a dialog for notifying of missing updown scripts.
     Returns that dialog.
 
     :rtype: QtGui.QMessageBox instance
@@ -102,7 +100,7 @@ def get_missing_updown_dialog():
 
 def check_missing():
     """
-    Checks for the need of installing missing scripts, and
+    Check for the need of installing missing scripts, and
     raises a dialog to ask user for permission to do it.
     """
     config = LeapSettings()
@@ -150,7 +148,7 @@ def check_missing():
 
 def _windows_has_tap_device():
     """
-    Loops over the windows registry trying to find if the tap0901 tap driver
+    Loop over the windows registry trying to find if the tap0901 tap driver
     has been installed on this machine.
     """
     import _winreg as reg
@@ -176,7 +174,7 @@ def _windows_has_tap_device():
 
 def WindowsInitializer():
     """
-    Raises a dialog in case that the windows tap driver has not been found
+    Raise a dialog in case that the windows tap driver has not been found
     in the registry, asking the user for permission to install the driver
     """
     if not _windows_has_tap_device():
@@ -220,7 +218,7 @@ def WindowsInitializer():
 
 def _darwin_has_tun_kext():
     """
-    Returns True only if we found a directory under the system kext folder
+    Return True only if we found a directory under the system kext folder
     containing a kext named tun.kext, AND we found a startup item named 'tun'
     """
     # XXX we should be smarter here and use kextstats output.
@@ -236,7 +234,7 @@ def _darwin_has_tun_kext():
 
 def _darwin_install_missing_scripts(badexec, notfound):
     """
-    Tries to install the missing up/down scripts.
+    Try to install the missing up/down scripts.
 
     :param badexec: error for notifying execution error during command.
     :type badexec: str
@@ -291,7 +289,7 @@ def _darwin_install_missing_scripts(badexec, notfound):
 
 def DarwinInitializer():
     """
-    Raises a dialog in case that the osx tuntap driver has not been found
+    Raise a dialog in case that the osx tuntap driver has not been found
     in the registry, asking the user for permission to install the driver
     """
     # XXX split this function into several
@@ -345,9 +343,49 @@ def DarwinInitializer():
 #
 # Linux initializers
 #
+
+def _get_missing_resolvconf_dialog():
+    """
+    Create a dialog for notifying about missing openresolv.
+
+    :rtype: QtGui.QMessageBox instance
+    """
+    NO_RESOLVCONF = (
+        "Could not find <b>resolvconf</b> installed in your system.\n"
+        "Do you want to quit Bitmask now?")
+
+    EXPLAIN = (
+        "Encrypted Internet needs resolvconf installed to work properly.\n"
+        "Please use your package manager to install it.\n")
+
+    msg = QtGui.QMessageBox()
+    msg.setWindowTitle(msg.tr("Missing resolvconf framework"))
+    msg.setText(msg.tr(NO_RESOLVCONF))
+    # but maybe the user really deserve to know more
+    msg.setInformativeText(msg.tr(EXPLAIN))
+    msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+    msg.setDefaultButton(QtGui.QMessageBox.Yes)
+    return msg
+
+
+def _linux_check_resolvconf():
+    """
+    Raise a dialog warning about the lack of the resolvconf framework.
+    """
+    RESOLVCONF_PATH = "/sbin/resolvconf"
+    missing = not os.path.isfile(RESOLVCONF_PATH)
+
+    if missing:
+        msg = _get_missing_resolvconf_dialog()
+        ret = msg.exec_()
+
+        if ret == QtGui.QMessageBox.Yes:
+            sys.exit()
+
+
 def _linux_install_missing_scripts(badexec, notfound):
     """
-    Tries to install the missing up/down scripts.
+    Try to install the missing up/down scripts.
 
     :param badexec: error for notifying execution error during command.
     :type badexec: str
@@ -398,7 +436,11 @@ def _linux_install_missing_scripts(badexec, notfound):
 
 def LinuxInitializer():
     """
-    Raises a dialog in case that either updown scripts or policykit file
-    are missing or they have incorrect permissions.
+    Raise a dialog if needed files are missing.
+
+    Missing files can be either system-wide resolvconf, bitmask-root, or
+    policykit file. The dialog will also be raised if some of those files are
+    found to have incorrect permissions.
     """
+    _linux_check_resolvconf()
     check_missing()
diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py
new file mode 100644
index 0000000000000000000000000000000000000000..a88211607836badd7870d2f254dae7f1f371ea49
--- /dev/null
+++ b/src/leap/bitmask/services/eip/conductor.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+# conductor.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/>.
+"""
+EIP Conductor module.
+"""
+import logging
+
+from PySide import QtCore
+
+from leap.bitmask.gui import statemachines
+from leap.bitmask.services import EIP_SERVICE
+from leap.bitmask.services import get_service_display_name
+from leap.bitmask.services.eip.connection import EIPConnection
+from leap.bitmask.platform_init import IS_MAC
+
+QtDelayedCall = QtCore.QTimer.singleShot
+logger = logging.getLogger(__name__)
+
+
+class EIPConductor(object):
+
+    def __init__(self, settings, backend, **kwargs):
+        """
+        Initializes EIP Conductor.
+
+        :param settings:
+        :type settings:
+
+        :param backend:
+        :type backend:
+        """
+        self.eip_connection = EIPConnection()
+        self.eip_name = get_service_display_name(EIP_SERVICE)
+        self._settings = settings
+        self._backend = backend
+
+        self._eip_status = None
+
+    @property
+    def qtsigs(self):
+        return self.eip_connection.qtsigs
+
+    def add_eip_widget(self, widget):
+        """
+        Keep a reference to the passed eip status widget.
+
+        :param widget: the EIP Status widget.
+        :type widget: QWidget
+        """
+        self._eip_status = widget
+
+    def connect_signals(self):
+        """
+        Connect signals.
+        """
+        self.qtsigs.connecting_signal.connect(self._start_eip)
+
+        self.qtsigs.disconnecting_signal.connect(self._stop_eip)
+        self.qtsigs.disconnected_signal.connect(self._eip_status.eip_stopped)
+
+    def connect_backend_signals(self):
+        """
+        Connect to backend signals.
+        """
+        signaler = self._backend.signaler
+
+        # for conductor
+        signaler.eip_process_restart_tls.connect(self._do_eip_restart)
+        signaler.eip_process_restart_tls.connect(self._do_eip_failed)
+        signaler.eip_process_restart_ping.connect(self._do_eip_restart)
+        signaler.eip_process_finished.connect(self._eip_finished)
+
+        # for widget
+        self._eip_status.connect_backend_signals()
+
+    def start_eip_machine(self, action):
+        """
+        Initializes and starts the EIP state machine.
+        Needs the reference to the eip_status widget not to be empty.
+
+        :action: QtAction
+        """
+        action = action
+        button = self._eip_status.eip_button
+        label = self._eip_status.eip_label
+
+        builder = statemachines.ConnectionMachineBuilder(self.eip_connection)
+        eip_machine = builder.make_machine(button=button,
+                                           action=action,
+                                           label=label)
+        self.eip_machine = eip_machine
+        self.eip_machine.start()
+        logger.debug('eip machine started')
+
+    def do_connect(self):
+        """
+        Start the connection procedure.
+        Emits a signal that triggers the OFF -> Connecting sequence.
+        This will call _start_eip via the state machine.
+        """
+        self.qtsigs.do_connect_signal.emit()
+
+    def tear_fw_down(self):
+        """
+        Tear the firewall down.
+        """
+        self._backend.tear_fw_down()
+
+    @QtCore.Slot()
+    def _start_eip(self):
+        """
+        Starts EIP.
+        """
+        st = self._eip_status
+        is_restart = st and st.is_restart
+
+        def reconnect():
+            self.qtsigs.disconnecting_signal.connect(self._stop_eip)
+
+        if is_restart:
+            QtDelayedCall(0, reconnect)
+        else:
+            self._eip_status.eip_pre_up()
+        self.user_stopped_eip = False
+        self._eip_status.hide_fw_down_button()
+
+        # Until we set an option in the preferences window, we'll assume that
+        # by default we try to autostart. If we switch it off manually, it
+        # won't try the next time.
+        self._settings.set_autostart_eip(True)
+        self._eip_status.is_restart = False
+
+        # DO the backend call!
+        self._backend.eip_start(restart=is_restart)
+
+    def reconnect_stop_signal(self):
+        """
+        Restore the original behaviour associated with the disconnecting
+        signal, this is, trigger a normal stop, and not a restart one.
+        """
+
+        def do_stop(*args):
+            self._stop_eip(restart=False)
+
+        self.qtsigs.disconnecting_signal.disconnect()
+        self.qtsigs.disconnecting_signal.connect(do_stop)
+
+    @QtCore.Slot()
+    def _stop_eip(self, restart=False, failed=False):
+        """
+        TRIGGERS:
+          self.qsigs.do_disconnect_signal (via state machine)
+
+        Stops vpn process and makes gui adjustments to reflect
+        the change of state.
+
+        :param restart: whether this is part of a eip restart.
+        :type restart: bool
+
+        :param failed: whether this is the final step of a retry sequence
+        :type failed: bool
+        """
+        self._eip_status.is_restart = restart
+        self.user_stopped_eip = not restart and not failed
+
+        def on_disconnected_do_restart():
+            # hard restarts
+            logger.debug("HARD RESTART")
+            eip_status_label = self._eip_status.tr("{0} is restarting")
+            eip_status_label = eip_status_label.format(self.eip_name)
+            self._eip_status.eip_stopped(restart=True)
+            self._eip_status.set_eip_status(eip_status_label, error=False)
+
+            QtDelayedCall(2000, self.do_connect)
+
+        def plug_restart_on_disconnected():
+            self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart)
+
+        def reconnect_disconnected_signal():
+            self.qtsigs.disconnected_signal.disconnect(
+                on_disconnected_do_restart)
+
+        def do_stop(*args):
+            self._stop_eip(restart=False)
+
+        if restart:
+            # we bypass the on_eip_disconnected here
+            plug_restart_on_disconnected()
+            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)
+
+        elif failed:
+            self.qtsigs.disconnected_signal.emit()
+
+        else:
+            logger.debug('Setting autostart to: False')
+            self._settings.set_autostart_eip(False)
+
+        # Call to the backend.
+        self._backend.eip_stop(restart=restart)
+
+        # ... and inform the status widget
+        self._eip_status.set_eipstatus_off(False)
+        self._eip_status.eip_stopped(restart=restart, failed=failed)
+
+        self._already_started_eip = False
+
+        # XXX needed?
+        if restart:
+            QtDelayedCall(2000, self.reconnect_stop_signal)
+
+    @QtCore.Slot()
+    def _do_eip_restart(self):
+        """
+        TRIGGERS:
+            self._eip_connection.qtsigs.process_restart
+
+        Restart the connection.
+        """
+        if self._eip_status is not None:
+            self._eip_status.is_restart = True
+
+        def do_stop(*args):
+            self._stop_eip(restart=True)
+
+        try:
+            self.qtsigs.disconnecting_signal.disconnect()
+        except Exception:
+            logger.error("cannot disconnect signals")
+
+        self.qtsigs.disconnecting_signal.connect(do_stop)
+        self.qtsigs.do_disconnect_signal.emit()
+
+    @QtCore.Slot()
+    def _do_eip_failed(self):
+        """
+        Stop EIP after a failure to start.
+
+        TRIGGERS
+            signaler.eip_process_restart_tls
+        """
+        logger.debug("TLS Error: eip_stop (failed)")
+        self.qtsigs.connection_died_signal.emit()
+        QtDelayedCall(1000, self._eip_status.eip_failed_to_connect)
+
+    @QtCore.Slot(int)
+    def _eip_finished(self, exitCode):
+        """
+        TRIGGERS:
+            Signaler.eip_process_finished
+
+        Triggered when the EIP/VPN process finishes to set the UI
+        accordingly.
+
+        Ideally we would have the right exit code here,
+        but the use of different wrappers (pkexec, cocoasudo) swallows
+        the openvpn exit code so we get zero exit in some cases  where we
+        shouldn't. As a workaround we just use a flag to indicate
+        a purposeful switch off, and mark everything else as unexpected.
+
+        :param exitCode: the exit code of the eip process.
+        :type exitCode: int
+        """
+        # TODO Add error catching to the openvpn log observer
+        # so we can have a more precise idea of which type
+        # of error did we have (server side, local problem, etc)
+
+        logger.info("VPN process finished with exitCode %s..."
+                    % (exitCode,))
+
+        signal = self.qtsigs.disconnected_signal
+
+        # XXX check if these exitCodes are pkexec/cocoasudo specific
+        if exitCode in (126, 127):
+            eip_status_label = self._eip_status.tr(
+                "{0} could not be launched "
+                "because you did not authenticate properly.")
+            eip_status_label = eip_status_label.format(self.eip_name)
+            self._eip_status.set_eip_status(eip_status_label, error=True)
+            signal = self.qtsigs.connection_aborted_signal
+            self._backend.eip_terminate()
+
+        # 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:
+            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)
+            signal = self.qtsigs.connection_died_signal
+            self._eip_status.show_fw_down_button()
+            self._eip_status.eip_failed_to_connect()
+
+        if exitCode == 0 and IS_MAC:
+            # XXX remove this warning after I fix cocoasudo.
+            logger.warning("The above exit code MIGHT BE WRONG.")
+
+        # We emit signals to trigger transitions in the state machine:
+        signal.emit()
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index a03bfc44065fe69d364d7dfcf2a2c736c7fc1571..41d75052fd0f60c0f5cf1cbe9e8ab2996d9d4478 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -52,6 +52,8 @@ class DarwinVPNLauncher(VPNLauncher):
     OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
     OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % (
         INSTALL_PATH_ESCAPED,)
+    OPENVPN_BIN_PATH = "%s/Contents/Resources/%s" % (INSTALL_PATH,
+                                                     OPENVPN_BIN)
 
     UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,)
     DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,)
diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py
index 09a3d25744690eaa3da893a1ccaa46b71b6a757d..e7419b22dbf1594edc47595dd1917b64c1fa21d6 100644
--- a/src/leap/bitmask/services/eip/eipconfig.py
+++ b/src/leap/bitmask/services/eip/eipconfig.py
@@ -110,7 +110,7 @@ class VPNGatewaySelector(object):
 
     def get_gateways_list(self):
         """
-        Returns the existing gateways, sorted by timezone proximity.
+        Return the existing gateways, sorted by timezone proximity.
 
         :rtype: list of tuples (location, ip)
                 (str, IPv4Address or IPv6Address object)
@@ -148,16 +148,36 @@ class VPNGatewaySelector(object):
 
     def get_gateways(self):
         """
-        Returns the 4 best gateways, sorted by timezone proximity.
+        Return the 4 best gateways, sorted by timezone proximity.
 
         :rtype: list of IPv4Address or IPv6Address object.
         """
         gateways = [ip for location, ip in self.get_gateways_list()][:4]
         return gateways
 
+    def get_gateways_country_code(self):
+        """
+        Return a dict with ipaddress -> country code mapping.
+
+        :rtype: dict
+        """
+        country_codes = {}
+
+        locations = self._eipconfig.get_locations()
+        gateways = self._eipconfig.get_gateways()
+
+        for idx, gateway in enumerate(gateways):
+            gateway_location = gateway.get('location')
+
+            ip = self._eipconfig.get_gateway_ip(idx)
+            if gateway_location is not None:
+                ccode = locations[gateway['location']]['country_code']
+                country_codes[ip] = ccode
+        return country_codes
+
     def _get_timezone_distance(self, offset):
         '''
-        Returns the distance between the local timezone and
+        Return the distance between the local timezone and
         the one with offset 'offset'.
 
         :param offset: the distance of a timezone to GMT.
@@ -179,7 +199,7 @@ class VPNGatewaySelector(object):
 
     def _get_local_offset(self):
         '''
-        Returns the distance between GMT and the local timezone.
+        Return the distance between GMT and the local timezone.
 
         :rtype: int
         '''
diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
index 1f0813e02aea9b3c3e75e2d3668c184305babc9e..955768d110c39c34b614a4b8351d621baab4a75f 100644
--- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -63,14 +63,20 @@ def _is_auth_agent_running():
     :return: True if it's running, False if it's not.
     :rtype: boolean
     """
+    # Note that gnome-shell does not uses a separate process for the
+    # polkit-agent, it uses a polkit-agent within its own process so we can't
+    # ps-grep a polkit process, we can ps-grep gnome-shell itself.
+
     # the [x] thing is to avoid grep match itself
     polkit_options = [
         'ps aux | grep "polkit-[g]nome-authentication-agent-1"',
         'ps aux | grep "polkit-[k]de-authentication-agent-1"',
         'ps aux | grep "polkit-[m]ate-authentication-agent-1"',
-        'ps aux | grep "[l]xpolkit"'
+        'ps aux | grep "[l]xpolkit"',
+        'ps aux | grep "[g]nome-shell"',
     ]
     is_running = [commands.getoutput(cmd) for cmd in polkit_options]
+
     return any(is_running)
 
 
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index dcb48e8a41cf645d724e2f090c63e6bf9a3f4a9c..9629afae64f49c6a85ea3e36d9169208f0675da6 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -25,6 +25,7 @@ import stat
 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.config.providerconfig import ProviderConfig
 from leap.bitmask.platform_init import IS_LINUX
@@ -122,9 +123,9 @@ class VPNLauncher(object):
         leap_settings = LeapSettings()
         domain = providerconfig.get_domain()
         gateway_conf = leap_settings.get_selected_gateway(domain)
+        gateway_selector = VPNGatewaySelector(eipconfig)
 
         if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
-            gateway_selector = VPNGatewaySelector(eipconfig)
             gateways = gateway_selector.get_gateways()
         else:
             gateways = [gateway_conf]
@@ -133,6 +134,12 @@ 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
 
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index 1559ea8ba78d6735e9834c5d0d0ea3406f2303d4..f56d464efd44f15d6566358efe477495b7632089 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -17,6 +17,7 @@
 """
 VPN Manager, spawned in a custom processProtocol.
 """
+import commands
 import logging
 import os
 import shutil
@@ -30,9 +31,11 @@ import psutil
 try:
     # psutil < 2.0.0
     from psutil.error import AccessDenied as psutil_AccessDenied
+    PSUTIL_2 = False
 except ImportError:
     # psutil >= 2.0.0
     from psutil import AccessDenied as psutil_AccessDenied
+    PSUTIL_2 = True
 
 from leap.bitmask.config import flags
 from leap.bitmask.config.providerconfig import ProviderConfig
@@ -67,7 +70,7 @@ class VPNObserver(object):
         'NETWORK_UNREACHABLE': (
             'Network is unreachable (code=101)',),
         'PROCESS_RESTART_TLS': (
-            "SIGUSR1[soft,tls-error]",),
+            "SIGTERM[soft,tls-error]",),
         'PROCESS_RESTART_PING': (
             "SIGTERM[soft,ping-restart]",),
         'INITIALIZATION_COMPLETED': (
@@ -113,10 +116,12 @@ class VPNObserver(object):
         :returns: a Signaler signal or None
         :rtype: str or None
         """
+        sig = self._signaler
         signals = {
-            "network_unreachable": self._signaler.EIP_NETWORK_UNREACHABLE,
-            "process_restart_tls": self._signaler.EIP_PROCESS_RESTART_TLS,
-            "process_restart_ping": self._signaler.EIP_PROCESS_RESTART_PING,
+            "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())
 
@@ -178,6 +183,8 @@ class VPN(object):
         kwargs['openvpn_verb'] = self._openvpn_verb
         kwargs['signaler'] = self._signaler
 
+        restart = kwargs.pop('restart', False)
+
         # start the main vpn subprocess
         vpnproc = VPNProcess(*args, **kwargs)
 
@@ -188,8 +195,9 @@ class VPN(object):
         # we try to bring the firewall up
         if IS_LINUX:
             gateways = vpnproc.getGateways()
-            firewall_up = self._launch_firewall(gateways)
-            if not firewall_up:
+            firewall_up = self._launch_firewall(gateways,
+                                                restart=restart)
+            if not restart and not firewall_up:
                 logger.error("Could not bring firewall up, "
                              "aborting openvpn launch.")
                 return
@@ -211,7 +219,7 @@ class VPN(object):
         self._pollers.extend(poll_list)
         self._start_pollers()
 
-    def _launch_firewall(self, gateways):
+    def _launch_firewall(self, gateways, restart=False):
         """
         Launch the firewall using the privileged wrapper.
 
@@ -226,11 +234,24 @@ class VPN(object):
         # XXX could check that the iptables rules are in place.
 
         BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
-        exitCode = subprocess.call(["pkexec",
-                                    BM_ROOT, "firewall", "start"] + gateways)
+        cmd = ["pkexec", BM_ROOT, "firewall", "start"]
+        if restart:
+            cmd.append("restart")
+        exitCode = subprocess.call(cmd + gateways)
         return True if exitCode is 0 else False
 
-    def _tear_down_firewall(self):
+    def is_fw_down(self):
+        """
+        Return whether the firewall is down or not.
+
+        :rtype: bool
+        """
+        BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+        fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT)
+        fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256
+        return fw_is_down()
+
+    def tear_down_firewall(self):
         """
         Tear the firewall down using the privileged wrapper.
         """
@@ -254,7 +275,7 @@ class VPN(object):
 
                 # we try to tear the firewall down
                 if IS_LINUX and self._user_stopped:
-                    firewall_down = self._tear_down_firewall()
+                    firewall_down = self.tear_down_firewall()
                     if firewall_down:
                         logger.debug("Firewall down")
                     else:
@@ -286,22 +307,28 @@ class VPN(object):
             self._vpnproc.aborted = True
             self._vpnproc.killProcess()
 
-    def terminate(self, shutdown=False):
+    def terminate(self, shutdown=False, restart=False):
         """
         Stops the openvpn subprocess.
 
         Attempts to send a SIGTERM first, and after a timeout
         it sends a SIGKILL.
+
+        :param shutdown: whether this is the final shutdown
+        :type shutdown: bool
+        :param restart: whether this stop is part of a hard restart.
+        :type restart: bool
         """
         from twisted.internet import reactor
         self._stop_pollers()
 
-        # We assume that the only valid shutodowns are initiated
-        # by an user action.
-        self._user_stopped = shutdown
-
         # First we try to be polite and send a SIGTERM...
-        if self._vpnproc:
+        if self._vpnproc is not None:
+            # We assume that the only valid stops are initiated
+            # by an user action, not hard restarts
+            self._user_stopped = not restart
+            self._vpnproc.is_restart = restart
+
             self._sentterm = True
             self._vpnproc.terminate_openvpn(shutdown=shutdown)
 
@@ -310,13 +337,12 @@ class VPN(object):
             reactor.callLater(
                 self.TERMINATE_WAIT, self._kill_if_left_alive)
 
-            if shutdown:
-                if IS_LINUX and self._user_stopped:
-                    firewall_down = self._tear_down_firewall()
-                    if firewall_down:
-                        logger.debug("Firewall down")
-                    else:
-                        logger.warning("Could not tear firewall down")
+            if IS_LINUX and self._user_stopped:
+                firewall_down = self.tear_down_firewall()
+                if firewall_down:
+                    logger.debug("Firewall down")
+                else:
+                    logger.warning("Could not tear firewall down")
 
     def _start_pollers(self):
         """
@@ -676,7 +702,13 @@ class VPNManager(object):
                 # we need to be able to filter out arguments in the form
                 # --openvpn-foo, since otherwise we are shooting ourselves
                 # in the feet.
-                if any(map(lambda s: s.find("LEAPOPENVPN") != -1, p.cmdline)):
+
+                if PSUTIL_2:
+                    cmdline = p.cmdline()
+                else:
+                    cmdline = p.cmdline
+                if any(map(lambda s: s.find(
+                        "LEAPOPENVPN") != -1, cmdline)):
                     openvpn_process = p
                     break
             except psutil_AccessDenied:
@@ -731,7 +763,7 @@ class VPNManager(object):
                 # However, that should be a rare case right now.
                 self._send_command("signal SIGTERM")
                 self._close_management_socket(announce=True)
-            except Exception as e:
+            except (Exception, AssertionError) as e:
                 logger.warning("Problem trying to terminate OpenVPN: %r"
                                % (e,))
         else:
@@ -800,6 +832,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
         self._openvpn_verb = openvpn_verb
 
         self._vpn_observer = VPNObserver(signaler)
+        self.is_restart = False
 
     # processProtocol methods
 
@@ -835,7 +868,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
         exit_code = reason.value.exitCode
         if isinstance(exit_code, int):
             logger.debug("processExited, status %d" % (exit_code,))
-        self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code)
+        self._signaler.signal(
+            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 1766a39db0d4db6abcaa81e3cb23cbe91f57bdca..98b40929a43070a6ebd2061ed0bb63a5afc295d4 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -19,15 +19,10 @@ Mail Services Conductor
 """
 import logging
 
-from zope.proxy import sameProxiedObjects
-
+from leap.bitmask.config import flags
 from leap.bitmask.gui import statemachines
 from leap.bitmask.services.mail import connection as mail_connection
-from leap.bitmask.services.mail import imap
-from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
-from leap.bitmask.services.mail.smtpconfig import SMTPConfig
 
-from leap.common.check import leap_assert
 from leap.common.events import events_pb2 as leap_events
 from leap.common.events import register as leap_register
 
@@ -44,9 +39,6 @@ class IMAPControl(object):
         Initializes smtp variables.
         """
         self.imap_machine = None
-        self.imap_service = None
-        self.imap_port = None
-        self.imap_factory = None
         self.imap_connection = None
 
         leap_register(signal=leap_events.IMAP_SERVICE_STARTED,
@@ -55,10 +47,13 @@ class IMAPControl(object):
         leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START,
                       callback=self._handle_imap_events,
                       reqcbk=lambda req, resp: None)
+        leap_register(signal=leap_events.IMAP_CLIENT_LOGIN,
+                      callback=self._handle_imap_events,
+                      reqcbk=lambda req, resp: None)
 
     def set_imap_connection(self, imap_connection):
         """
-        Sets the imap connection to an initialized connection.
+        Set the imap connection to an initialized connection.
 
         :param imap_connection: an initialized imap connection
         :type imap_connection: IMAPConnection instance.
@@ -67,67 +62,18 @@ class IMAPControl(object):
 
     def start_imap_service(self):
         """
-        Starts imap service.
+        Start imap service.
         """
-        from leap.bitmask.config import flags
-
-        logger.debug('Starting imap service')
-        leap_assert(sameProxiedObjects(self._soledad, None)
-                    is not True,
-                    "We need a non-null soledad for initializing imap service")
-        leap_assert(sameProxiedObjects(self._keymanager, None)
-                    is not True,
-                    "We need a non-null keymanager for initializing imap "
-                    "service")
-
-        offline = flags.OFFLINE
-        self.imap_service, self.imap_port, \
-            self.imap_factory = imap.start_imap_service(
-                self._soledad,
-                self._keymanager,
-                userid=self.userid,
-                offline=offline)
+        self._backend.imap_start_service(self.userid, flags.OFFLINE)
 
-        if offline is False:
-            logger.debug("Starting loop")
-            self.imap_service.start_loop()
-
-    def stop_imap_service(self, cv):
+    def stop_imap_service(self):
         """
-        Stops imap service (fetcher, factory and port).
-
-        :param cv: A condition variable to which we can signal when imap
-                   indeed stops.
-        :type cv: threading.Condition
+        Stop imap service.
         """
         self.imap_connection.qtsigs.disconnecting_signal.emit()
-        # TODO We should homogenize both services.
-        if self.imap_service is not None:
-            logger.debug('Stopping imap service.')
-            # Stop the loop call in the fetcher
-            self.imap_service.stop()
-            self.imap_service = None
-            # Stop listening on the IMAP port
-            self.imap_port.stopListening()
-            # Stop the protocol
-            self.imap_factory.theAccount.closed = True
-            self.imap_factory.doStop(cv)
-        else:
-            # main window does not have to wait because there's no service to
-            # be stopped, so we release the condition variable
-            cv.acquire()
-            cv.notify()
-            cv.release()
-
-    def fetch_incoming_mail(self):
-        """
-        Fetches incoming mail.
-        """
-        if self.imap_service:
-            logger.debug('Client connected, fetching mail...')
-            self.imap_service.fetch()
-
-    # handle events
+        logger.debug('Stopping imap service.')
+
+        self._backend.imap_stop_service()
 
     def _handle_imap_events(self, req):
         """
@@ -137,25 +83,31 @@ class IMAPControl(object):
         :type req: leap.common.events.events_pb2.SignalRequest
         """
         if req.event == leap_events.IMAP_SERVICE_STARTED:
-            self.on_imap_connected()
+            self._on_imap_connected()
         elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START:
-            self.on_imap_failed()
+            self._on_imap_failed()
+        elif req.event == leap_events.IMAP_CLIENT_LOGIN:
+            self._on_mail_client_logged_in()
 
-    # emit connection signals
+    def _on_mail_client_logged_in(self):
+        """
+        On mail client logged in, fetch incoming mail.
+        """
+        self._controller.imap_service_fetch()
 
-    def on_imap_connecting(self):
+    def _on_imap_connecting(self):
         """
         Callback for IMAP connecting state.
         """
         self.imap_connection.qtsigs.connecting_signal.emit()
 
-    def on_imap_connected(self):
+    def _on_imap_connected(self):
         """
         Callback for IMAP connected state.
         """
         self.imap_connection.qtsigs.connected_signal.emit()
 
-    def on_imap_failed(self):
+    def _on_imap_failed(self):
         """
         Callback for IMAP failed state.
         """
@@ -167,12 +119,9 @@ class SMTPControl(object):
         """
         Initializes smtp variables.
         """
-        self.smtp_config = SMTPConfig()
         self.smtp_connection = None
         self.smtp_machine = None
 
-        self.smtp_bootstrapper = SMTPBootstrapper()
-
         leap_register(signal=leap_events.SMTP_SERVICE_STARTED,
                       callback=self._handle_smtp_events,
                       reqcbk=lambda req, resp: None)
@@ -188,29 +137,23 @@ class SMTPControl(object):
         """
         self.smtp_connection = smtp_connection
 
-    def start_smtp_service(self, provider_config, download_if_needed=False):
+    def start_smtp_service(self, download_if_needed=False):
         """
         Starts the SMTP service.
 
-        :param provider_config: Provider configuration
-        :type provider_config: ProviderConfig
         :param download_if_needed: True if it should check for mtime
                                    for the file
         :type download_if_needed: bool
         """
         self.smtp_connection.qtsigs.connecting_signal.emit()
-        self.smtp_bootstrapper.start_smtp_service(
-            provider_config, self.smtp_config, self._keymanager,
-            self.userid, download_if_needed)
+        self._backend.smtp_start_service(self.userid, download_if_needed)
 
     def stop_smtp_service(self):
         """
         Stops the SMTP service.
         """
         self.smtp_connection.qtsigs.disconnecting_signal.emit()
-        self.smtp_bootstrapper.stop_smtp_service()
-
-    # handle smtp events
+        self._backend.smtp_stop_service()
 
     def _handle_smtp_events(self, req):
         """
@@ -224,8 +167,6 @@ class SMTPControl(object):
         elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START:
             self.on_smtp_failed()
 
-    # emit connection signals
-
     def on_smtp_connecting(self):
         """
         Callback for SMTP connecting state.
@@ -253,22 +194,17 @@ class MailConductor(IMAPControl, SMTPControl):
     """
     # XXX We could consider to use composition instead of inheritance here.
 
-    def __init__(self, soledad, keymanager):
+    def __init__(self, backend):
         """
         Initializes the mail conductor.
 
-        :param soledad: a transparent proxy that eventually will point to a
-                        Soledad Instance.
-        :type soledad: zope.proxy.ProxyBase
-
-        :param keymanager: a transparent proxy that eventually will point to a
-                           Keymanager Instance.
-        :type keymanager: zope.proxy.ProxyBase
+        :param backend: Backend being used
+        :type backend: Backend
         """
         IMAPControl.__init__(self)
         SMTPControl.__init__(self)
-        self._soledad = soledad
-        self._keymanager = keymanager
+
+        self._backend = backend
         self._mail_machine = None
         self._mail_connection = mail_connection.MailConnection()
 
@@ -309,6 +245,13 @@ class MailConductor(IMAPControl, SMTPControl):
         self._smtp_machine = smtp
         self._smtp_machine.start()
 
+    def stop_mail_services(self):
+        """
+        Stop the IMAP and SMTP services.
+        """
+        self.stop_imap_service()
+        self.stop_smtp_service()
+
     def connect_mail_signals(self, widget):
         """
         Connects the mail signals to the mail_status widget slots.
diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0bf4c3449d406577052f8f21b10cea5af292094
--- /dev/null
+++ b/src/leap/bitmask/services/mail/imapcontroller.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# imapcontroller.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/>.
+"""
+IMAP service controller.
+"""
+import logging
+
+from leap.bitmask.services.mail import imap
+
+
+logger = logging.getLogger(__name__)
+
+
+class IMAPController(object):
+    """
+    IMAP Controller.
+    """
+    def __init__(self, soledad, keymanager):
+        """
+        Initialize IMAP variables.
+
+        :param soledad: a transparent proxy that eventually will point to a
+                        Soledad Instance.
+        :type soledad: zope.proxy.ProxyBase
+        :param keymanager: a transparent proxy that eventually will point to a
+                           Keymanager Instance.
+        :type keymanager: zope.proxy.ProxyBase
+        """
+        self._soledad = soledad
+        self._keymanager = keymanager
+
+        self.imap_service = None
+        self.imap_port = None
+        self.imap_factory = None
+
+    def start_imap_service(self, userid, offline=False):
+        """
+        Start IMAP service.
+
+        :param userid: user id, in the form "user@provider"
+        :type userid: str
+        :param offline: whether imap should start in offline mode or not.
+        :type offline: bool
+        """
+        logger.debug('Starting imap service')
+
+        self.imap_service, self.imap_port, \
+            self.imap_factory = imap.start_imap_service(
+                self._soledad,
+                self._keymanager,
+                userid=userid,
+                offline=offline)
+
+        if offline is False:
+            logger.debug("Starting loop")
+            self.imap_service.start_loop()
+
+    def stop_imap_service(self, cv):
+        """
+        Stop IMAP service (fetcher, factory and port).
+
+        :param cv: A condition variable to which we can signal when imap
+                   indeed stops.
+        :type cv: threading.Condition
+        """
+        if self.imap_service is not None:
+            # Stop the loop call in the fetcher
+            self.imap_service.stop()
+            self.imap_service = None
+
+            # Stop listening on the IMAP port
+            self.imap_port.stopListening()
+
+            # Stop the protocol
+            self.imap_factory.theAccount.closed = True
+            self.imap_factory.doStop(cv)
+        else:
+            # Release the condition variable so the caller doesn't have to wait
+            cv.acquire()
+            cv.notify()
+            cv.release()
+
+    def fetch_incoming_mail(self):
+        """
+        Fetch incoming mail.
+        """
+        if self.imap_service:
+            logger.debug('Client connected, fetching mail...')
+            self.imap_service.fetch()
diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py
index 7ecf813415d0bf52da0aa8278a67a3dc1f5c6680..3ef755e8eebf7e74addbaf904097d7caaeabc334 100644
--- a/src/leap/bitmask/services/mail/smtpbootstrapper.py
+++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py
@@ -28,7 +28,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig
 from leap.bitmask.util import is_file
 
 from leap.common import certs as leap_certs
-from leap.common.check import leap_assert, leap_assert_type
+from leap.common.check import leap_assert
 from leap.common.files import check_and_fix_urw_only
 
 logger = logging.getLogger(__name__)
@@ -38,6 +38,10 @@ class NoSMTPHosts(Exception):
     """This is raised when there is no SMTP host to use."""
 
 
+class MalformedUserId(Exception):
+    """This is raised when an userid does not have the form user@provider."""
+
+
 class SMTPBootstrapper(AbstractBootstrapper):
     """
     SMTP init procedure
@@ -126,15 +130,10 @@ class SMTPBootstrapper(AbstractBootstrapper):
             smtp_key=client_cert_path,
             encrypted_only=False)
 
-    def start_smtp_service(self, provider_config, smtp_config, keymanager,
-                           userid, download_if_needed=False):
+    def start_smtp_service(self, keymanager, userid, download_if_needed=False):
         """
         Starts the SMTP service.
 
-        :param provider_config: Provider configuration
-        :type provider_config: ProviderConfig
-        :param smtp_config: SMTP configuration to populate
-        :type smtp_config: SMTPConfig
         :param keymanager: a transparent proxy that eventually will point to a
                            Keymanager Instance.
         :type keymanager: zope.proxy.ProxyBase
@@ -144,13 +143,16 @@ class SMTPBootstrapper(AbstractBootstrapper):
                                    for the file
         :type download_if_needed: bool
         """
-        leap_assert_type(provider_config, ProviderConfig)
-        leap_assert_type(smtp_config, SMTPConfig)
+        try:
+            username, domain = userid.split('@')
+        except ValueError:
+            logger.critical("Malformed userid parameter!")
+            raise MalformedUserId()
 
-        self._provider_config = provider_config
+        self._provider_config = ProviderConfig.get_provider_config(domain)
         self._keymanager = keymanager
-        self._smtp_config = smtp_config
-        self._useid = userid
+        self._smtp_config = SMTPConfig()
+        self._userid = userid
         self._download_if_needed = download_if_needed
 
         try:
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index 6bb7c036d7c3b25407481dcfc0001451bcf9e602..db12fd8051edc8750ca5dbc71c2d996da8375db9 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -25,7 +25,6 @@ import sys
 from ssl import SSLError
 from sqlite3 import ProgrammingError as sqlite_ProgrammingError
 
-from PySide import QtCore
 from u1db import errors as u1db_errors
 from twisted.internet import threads
 from zope.proxy import sameProxiedObjects
@@ -134,16 +133,11 @@ class SoledadBootstrapper(AbstractBootstrapper):
     MAX_INIT_RETRIES = 10
     MAX_SYNC_RETRIES = 10
 
-    # All dicts returned are of the form
-    # {"passed": bool, "error": str}
-    download_config = QtCore.Signal(dict)
-    gen_key = QtCore.Signal(dict)
-    local_only_ready = QtCore.Signal(dict)
-    soledad_invalid_auth_token = QtCore.Signal()
-    soledad_failed = QtCore.Signal()
+    def __init__(self, signaler=None):
+        AbstractBootstrapper.__init__(self, signaler)
 
-    def __init__(self):
-        AbstractBootstrapper.__init__(self)
+        if signaler is not None:
+            self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP
 
         self._provider_config = None
         self._soledad_config = None
@@ -181,16 +175,23 @@ class SoledadBootstrapper(AbstractBootstrapper):
         Instantiate Soledad for offline use.
 
         :param username: full user id (user@provider)
-        :type username: basestring
+        :type username: str or unicode
         :param password: the soledad passphrase
         :type password: unicode
         :param uuid: the user uuid
-        :type uuid: basestring
+        :type uuid: str or unicode
         """
         print "UUID ", uuid
         self._address = username
+        self._password = password
         self._uuid = uuid
-        return self.load_and_sync_soledad(uuid, offline=True)
+        try:
+            self.load_and_sync_soledad(uuid, offline=True)
+            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)
 
     def _get_soledad_local_params(self, uuid, offline=False):
         """
@@ -245,7 +246,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
     def _do_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, token):
         """
-        Initialize soledad, retry if necessary and emit soledad_failed if we
+        Initialize soledad, retry if necessary and raise an exception if we
         can't succeed.
 
         :param uuid: user identifier
@@ -263,19 +264,22 @@ class SoledadBootstrapper(AbstractBootstrapper):
         :param auth token: auth token
         :type auth_token: str
         """
-        init_tries = self.MAX_INIT_RETRIES
-        while init_tries > 0:
+        init_tries = 1
+        while init_tries <= self.MAX_INIT_RETRIES:
             try:
+                logger.debug("Trying to init soledad....")
                 self._try_soledad_init(
                     uuid, secrets_path, local_db_path,
                     server_url, cert_file, token)
                 logger.debug("Soledad has been initialized.")
                 return
             except Exception:
-                init_tries -= 1
+                init_tries += 1
+                msg = "Init failed, retrying... (retry {0} of {1})".format(
+                    init_tries, self.MAX_INIT_RETRIES)
+                logger.warning(msg)
                 continue
 
-        self.soledad_failed.emit()
         raise SoledadInitError()
 
     def load_and_sync_soledad(self, uuid=None, offline=False):
@@ -306,9 +310,8 @@ class SoledadBootstrapper(AbstractBootstrapper):
         leap_assert(not sameProxiedObjects(self._soledad, None),
                     "Null soledad, error while initializing")
 
-        if flags.OFFLINE is True:
+        if flags.OFFLINE:
             self._init_keymanager(self._address, token)
-            self.local_only_ready.emit({self.PASSED_KEY: True})
         else:
             try:
                 address = make_address(
@@ -353,9 +356,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
         Do several retries to get an initial soledad sync.
         """
         # and now, let's sync
-        sync_tries = self.MAX_SYNC_RETRIES
-        while sync_tries > 0:
+        sync_tries = 1
+        while sync_tries <= self.MAX_SYNC_RETRIES:
             try:
+                logger.debug("Trying to sync soledad....")
                 self._try_soledad_sync()
                 logger.debug("Soledad has been synced.")
                 # so long, and thanks for all the fish
@@ -368,19 +372,20 @@ class SoledadBootstrapper(AbstractBootstrapper):
                 # retry strategy can be pushed to u1db, or at least
                 # it's something worthy to talk about with the
                 # ubuntu folks.
-                sync_tries -= 1
+                sync_tries += 1
+                msg = "Sync failed, retrying... (retry {0} of {1})".format(
+                    sync_tries, self.MAX_SYNC_RETRIES)
+                logger.warning(msg)
                 continue
             except InvalidAuthTokenError:
-                self.soledad_invalid_auth_token.emit()
+                self._signaler.signal(
+                    self._signaler.SOLEDAD_INVALID_AUTH_TOKEN)
                 raise
             except Exception as e:
                 logger.exception("Unhandled error while syncing "
                                  "soledad: %r" % (e,))
                 break
 
-        # reached bottom, failed to sync
-        # and there's nothing we can do...
-        self.soledad_failed.emit()
         raise SoledadSyncError()
 
     def _try_soledad_init(self, uuid, secrets_path, local_db_path,
@@ -443,7 +448,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
         Raises SoledadSyncError if not successful.
         """
         try:
-            logger.debug("trying to sync soledad....")
             self._soledad.sync()
         except SSLError as exc:
             logger.error("%r" % (exc,))
@@ -467,7 +471,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
         """
         Download the Soledad config for the given provider
         """
-
         leap_assert(self._provider_config,
                     "We need a provider configuration!")
         logger.debug("Downloading Soledad config for %s" %
@@ -480,14 +483,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
             self._session,
             self._download_if_needed)
 
-        # soledad config is ok, let's proceed to load and sync soledad
-        # XXX but honestly, this is a pretty strange entry point for that.
-        # it feels like it should be the other way around:
-        # load_and_sync, and from there, if needed, call download_config
-
-        uuid = self.srpauth.get_uuid()
-        self.load_and_sync_soledad(uuid)
-
     def _get_gpg_bin_path(self):
         """
         Return the path to gpg binary.
@@ -574,7 +569,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
                 logger.exception(exc)
                 # but we do not raise
 
-    def _gen_key(self, _):
+    def _gen_key(self):
         """
         Generates the key pair if needed, uploads it to the webapp and
         nickserver
@@ -613,10 +608,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
 
         logger.debug("Key generated successfully.")
 
-    def run_soledad_setup_checks(self,
-                                 provider_config,
-                                 user,
-                                 password,
+    def run_soledad_setup_checks(self, provider_config, user, password,
                                  download_if_needed=False):
         """
         Starts the checks needed for a new soledad setup
@@ -640,9 +632,27 @@ class SoledadBootstrapper(AbstractBootstrapper):
         self._user = user
         self._password = password
 
-        cb_chain = [
-            (self._download_config, self.download_config),
-            (self._gen_key, self.gen_key)
-        ]
+        if flags.OFFLINE:
+            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
 
-        return self.addCallbackChain(cb_chain)
+        try:
+            self._download_config()
+
+            # soledad config is ok, let's proceed to load and sync soledad
+            uuid = self.srpauth.get_uuid()
+            self.load_and_sync_soledad(uuid)
+
+            if not flags.OFFLINE:
+                self._gen_key()
+
+            self._signaler.signal(signal_finished)
+        except Exception as e:
+            # TODO: we should handle more specific exceptions in here
+            self._soledad = None
+            self._keymanager = None
+            logger.exception("Error while bootstrapping Soledad: %r" % (e, ))
+            self._signaler.signal(signal_failed)
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index 2b2cd874bf9c69ab641f9a0ec8aa057eb7fe768f..c35be99e6b0cb980ab9ce33ddbf95615bb340110 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -28,11 +28,6 @@ from leap.common.config import get_path_prefix as common_get_path_prefix
 # We'll give your money back if it does not alleviate the eye strain, at least.
 
 
-# levelname length == 8, since 'CRITICAL' is the longest
-LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - '
-              'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s')
-
-
 def first(things):
     """
     Return the head of a collection.
diff --git a/src/leap/bitmask/util/password.py b/src/leap/bitmask/util/credentials.py
similarity index 67%
rename from src/leap/bitmask/util/password.py
rename to src/leap/bitmask/util/credentials.py
index 73659f0d751ae072ef954e7890aee46fd4f84788..07ded17b619069a0e89dc9300e488a655b467467 100644
--- a/src/leap/bitmask/util/password.py
+++ b/src/leap/bitmask/util/credentials.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# password.py
+# credentials.py
 # Copyright (C) 2013 LEAP
 #
 # This program is free software: you can redistribute it and/or modify
@@ -16,14 +16,34 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-Password utilities
+Credentials utilities
 """
-from PySide import QtCore
+from PySide import QtCore, QtGui
 
 WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password")
+USERNAME_REGEX = r"^[A-Za-z][A-Za-z\d_\-\.]+[A-Za-z\d]$"
 
+USERNAME_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX))
 
-def basic_password_checks(username, password, password2):
+
+def username_checks(username):
+    # translation helper
+    _tr = QtCore.QObject().tr
+
+    message = None
+
+    if message is None and len(username) < 2:
+        message = _tr("Username must have at least 2 characters")
+
+    valid = USERNAME_VALIDATOR.validate(username, 0)
+    valid_username = valid[0] == QtGui.QValidator.State.Acceptable
+    if message is None and not valid_username:
+        message = _tr("Invalid username")
+
+    return message is None, message
+
+
+def password_checks(username, password, password2):
     """
     Performs basic password checks to avoid really easy passwords.
 
@@ -46,6 +66,9 @@ def basic_password_checks(username, password, password2):
     if message is None and password != password2:
         message = _tr("Passwords don't match")
 
+    if message is None and not password:
+        message = _tr("You can't use an empty password")
+
     if message is None and len(password) < 6:
         message = _tr("Password too short")