summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/services')
-rw-r--r--src/leap/bitmask/services/abstractbootstrapper.py33
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py2
-rw-r--r--src/leap/bitmask/services/mail/imap.py12
-rw-r--r--src/leap/bitmask/services/mail/repair.py234
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py58
5 files changed, 301 insertions, 38 deletions
diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py
index 6d4d319b..3bee8e01 100644
--- a/src/leap/bitmask/services/abstractbootstrapper.py
+++ b/src/leap/bitmask/services/abstractbootstrapper.py
@@ -25,6 +25,8 @@ import requests
from functools import partial
from PySide import QtCore
+
+from twisted.python import log
from twisted.internet import threads
from leap.common.check import leap_assert, leap_assert_type
@@ -40,10 +42,13 @@ class AbstractBootstrapper(QtCore.QObject):
PASSED_KEY = "passed"
ERROR_KEY = "error"
- def __init__(self, bypass_checks=False):
+ def __init__(self, signaler=None, bypass_checks=False):
"""
Constructor for the abstract bootstrapper
+ :param signaler: Signaler object used to receive notifications
+ from the backend
+ :type signaler: Signaler
:param bypass_checks: Set to true if the app should bypass
first round of checks for CA
certificates at bootstrap
@@ -71,6 +76,7 @@ class AbstractBootstrapper(QtCore.QObject):
self._bypass_checks = bypass_checks
self._signal_to_emit = None
self._err_msg = None
+ self._signaler = signaler
def _gui_errback(self, failure):
"""
@@ -89,10 +95,20 @@ class AbstractBootstrapper(QtCore.QObject):
err_msg = self._err_msg \
if self._err_msg is not None \
else str(failure.value)
- self._signal_to_emit.emit({
+ data = {
self.PASSED_KEY: False,
self.ERROR_KEY: err_msg
- })
+ }
+ # TODO: Remove this check when all the bootstrappers are
+ # in the backend form
+ if isinstance(self._signal_to_emit, basestring):
+ if self._signaler is not None:
+ self._signaler.signal(self._signal_to_emit, data)
+ else:
+ logger.warning("Tried to notify but no signaler found")
+ else:
+ self._signal_to_emit.emit(data)
+ log.err(failure)
failure.trap(Exception)
def _errback(self, failure, signal=None):
@@ -127,8 +143,15 @@ class AbstractBootstrapper(QtCore.QObject):
:param signal: Signal to emit if it fails here first
:type signal: QtCore.SignalInstance
"""
- if signal:
- signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""})
+ if signal is not None:
+ data = {self.PASSED_KEY: True, self.ERROR_KEY: ""}
+ if isinstance(signal, basestring):
+ if self._signaler is not None:
+ self._signaler.signal(signal, data)
+ else:
+ logger.warning("Tried to notify but no signaler found")
+ else:
+ signal.emit(data)
def _callback_threader(self, cb, res, *args, **kwargs):
return threads.deferToThread(cb, res, *args, **kwargs)
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index fe3fe4c1..a03bfc44 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -95,7 +95,7 @@ class DarwinVPNLauncher(VPNLauncher):
resources_path = os.path.abspath(
os.path.join(os.getcwd(), "../../Contents/Resources"))
- return os.path.join(resources_path, "leap-client.tiff")
+ return os.path.join(resources_path, "bitmask.tiff")
@classmethod
def get_cocoasudo_ovpn_cmd(kls):
diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py
index 2667f156..5db18cb9 100644
--- a/src/leap/bitmask/services/mail/imap.py
+++ b/src/leap/bitmask/services/mail/imap.py
@@ -19,10 +19,10 @@ Initialization of imap service
"""
import logging
import os
-#import sys
+import sys
from leap.mail.imap.service import imap
-#from twisted.python import log
+from twisted.python import log
logger = logging.getLogger(__name__)
@@ -58,15 +58,15 @@ def start_imap_service(*args, **kwargs):
:returns: twisted.internet.task.LoopingCall instance
"""
+ from leap.bitmask.config import flags
logger.debug('Launching imap service')
override_period = get_mail_check_period()
if override_period:
kwargs['check_period'] = override_period
- # Uncomment the next two lines to get a separate debugging log
- # TODO handle this by a separate flag.
- #log.startLogging(open('/tmp/leap-imap.log', 'w'))
- #log.startLogging(sys.stdout)
+ if flags.MAIL_LOGFILE:
+ log.startLogging(open(flags.MAIL_LOGFILE, 'w'))
+ log.startLogging(sys.stdout)
return imap.run_service(*args, **kwargs)
diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py
new file mode 100644
index 00000000..767df1ef
--- /dev/null
+++ b/src/leap/bitmask/services/mail/repair.py
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+# repair.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/>.
+"""
+Utils for repairing mailbox indexes.
+"""
+import logging
+import getpass
+import os
+
+from collections import defaultdict
+
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.crypto.srpauth import SRPAuth
+from leap.bitmask.util import get_path_prefix
+from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths
+
+from leap.mail.imap.server import SoledadBackedAccount
+from leap.soledad.client import Soledad
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+
+
+def initialize_soledad(uuid, email, passwd,
+ secrets, localdb,
+ gnupg_home, tempdir):
+ """
+ Initializes soledad by hand
+
+ :param email: ID for the user
+ :param gnupg_home: path to home used by gnupg
+ :param tempdir: path to temporal dir
+ :rtype: Soledad instance
+ """
+ # XXX TODO unify with an authoritative source of mocks
+ # for soledad (or partial initializations).
+ # This is copied from the imap tests.
+
+ server_url = "http://provider"
+ cert_file = ""
+
+ class Mock(object):
+ def __init__(self, return_value=None):
+ self._return = return_value
+
+ def __call__(self, *args, **kwargs):
+ return self._return
+
+ class MockSharedDB(object):
+
+ get_doc = Mock()
+ put_doc = Mock()
+ lock = Mock(return_value=('atoken', 300))
+ unlock = Mock(return_value=True)
+
+ def __call__(self):
+ return self
+
+ Soledad._shared_db = MockSharedDB()
+ soledad = Soledad(
+ uuid,
+ passwd,
+ secrets,
+ localdb,
+ server_url,
+ cert_file)
+
+ return soledad
+
+
+class MBOXPlumber(object):
+ """
+ An class that can fix things inside a soledadbacked account.
+ The idea is to gather in this helper different fixes for mailboxes
+ that can be invoked when data migration in the client is needed.
+ """
+
+ def __init__(self, userid, passwd):
+ """
+ Initializes the plumber with all that's needed to authenticate
+ against the provider.
+
+ :param userid: user identifier, foo@bar
+ :type userid: basestring
+ :param passwd: the soledad passphrase
+ :type passwd: basestring
+ """
+ self.userid = userid
+ self.passwd = passwd
+ user, provider = userid.split('@')
+ self.user = user
+ self.sol = None
+ provider_config_path = os.path.join(
+ get_path_prefix(),
+ "leap", "providers",
+ provider, "provider.json")
+ provider_config = ProviderConfig()
+ loaded = provider_config.load(provider_config_path)
+ if not loaded:
+ print "could not load provider config!"
+ return self.exit()
+
+ self.srp = SRPAuth(provider_config)
+ self.srp.authentication_finished.connect(self.repair_account)
+
+ def start_auth(self):
+ """
+ returns the user identifier for a given provider.
+
+ :param provider: the provider to which we authenticate against.
+ """
+ print "Authenticating with provider..."
+ self.d = self.srp.authenticate(self.user, self.passwd)
+
+ def repair_account(self, *args):
+ """
+ Gets the user id for this account.
+ """
+ print "Got authenticated."
+ self.uid = self.srp.get_uid()
+ if not self.uid:
+ print "Got BAD UID from provider!"
+ return self.exit()
+ print "UID: %s" % (self.uid)
+
+ secrets, localdb = get_db_paths(self.uid)
+
+ self.sol = initialize_soledad(
+ self.uid, self.userid, self.passwd,
+ secrets, localdb, "/tmp", "/tmp")
+
+ self.acct = SoledadBackedAccount(self.userid, self.sol)
+ for mbox_name in self.acct.mailboxes:
+ self.repair_mbox(mbox_name)
+ print "done."
+ self.exit()
+
+ def repair_mbox(self, mbox_name):
+ """
+ Repairs indexes for a given mbox
+
+ :param mbox_name: mailbox to repair
+ :type mbox_name: basestring
+ """
+ print
+ print "REPAIRING INDEXES FOR MAILBOX %s" % (mbox_name,)
+ print "----------------------------------------------"
+ mbox = self.acct.getMailbox(mbox_name)
+ len_mbox = mbox.getMessageCount()
+ print "There are %s messages" % (len_mbox,)
+
+ last_ok = True if mbox.last_uid == len_mbox else False
+ uids_iter = (doc.content['uid'] for doc in mbox.messages.get_all())
+ dupes = self._has_dupes(uids_iter)
+ if last_ok and not dupes:
+ print "Mbox does not need repair."
+ return
+
+ msgs = mbox.messages.get_all()
+ for zindex, doc in enumerate(msgs):
+ mindex = zindex + 1
+ old_uid = doc.content['uid']
+ doc.content['uid'] = mindex
+ self.sol.put_doc(doc)
+ print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid)
+
+ old_last_uid = mbox.last_uid
+ mbox.last_uid = len_mbox
+ print "LAST UID: %s (%s)" % (mbox.last_uid, old_last_uid)
+
+ def _has_dupes(self, sequence):
+ """
+ Returns True if the given sequence of ints has duplicates.
+
+ :param sequence: a sequence of ints
+ :type sequence: sequence
+ :rtype: bool
+ """
+ d = defaultdict(lambda: 0)
+ for uid in sequence:
+ d[uid] += 1
+ if d[uid] != 1:
+ return True
+ return False
+
+ def exit(self):
+ from twisted.internet import reactor
+ self.d.cancel()
+ if self.sol:
+ self.sol.close()
+ try:
+ reactor.stop()
+ except Exception:
+ pass
+ return
+
+
+def repair_account(userid):
+ """
+ Starts repair process for a given account.
+ :param userid: the user id (email-like)
+ """
+ from twisted.internet import reactor
+ passwd = unicode(getpass.getpass("Passphrase: "))
+
+ # go mario!
+ plumber = MBOXPlumber(userid, passwd)
+ reactor.callLater(1, plumber.start_auth)
+ reactor.run()
+
+
+if __name__ == "__main__":
+ import sys
+
+ logging.basicConfig()
+
+ if len(sys.argv) != 2:
+ print "Usage: repair <username>"
+ sys.exit(1)
+ repair_account(sys.argv[1])
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index d078ae96..3ab62b2e 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -59,6 +59,33 @@ class SoledadInitError(Exception):
message = "Error while initializing Soledad"
+def get_db_paths(uuid):
+ """
+ Returns the secrets and local db paths needed for soledad
+ initialization
+
+ :param uuid: uuid for user
+ :type uuid: str
+
+ :return: a tuple with secrets, local_db paths
+ :rtype: tuple
+ """
+ prefix = os.path.join(get_path_prefix(), "leap", "soledad")
+ secrets = "%s/%s.secret" % (prefix, uuid)
+ local_db = "%s/%s.db" % (prefix, uuid)
+
+ # We remove an empty file if found to avoid complains
+ # about the db not being properly initialized
+ if is_file(local_db) and is_empty_file(local_db):
+ try:
+ os.remove(local_db)
+ except OSError:
+ logger.warning(
+ "Could not remove empty file %s"
+ % local_db)
+ return secrets, local_db
+
+
class SoledadBootstrapper(AbstractBootstrapper):
"""
Soledad init procedure
@@ -127,31 +154,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
"""
self._soledad_retries += 1
- def _get_db_paths(self, uuid):
- """
- Returns the secrets and local db paths needed for soledad
- initialization
-
- :param uuid: uuid for user
- :type uuid: str
-
- :return: a tuple with secrets, local_db paths
- :rtype: tuple
- """
- prefix = os.path.join(get_path_prefix(), "leap", "soledad")
- secrets = "%s/%s.secret" % (prefix, uuid)
- local_db = "%s/%s.db" % (prefix, uuid)
-
- # We remove an empty file if found to avoid complains
- # about the db not being properly initialized
- if is_file(local_db) and is_empty_file(local_db):
- try:
- os.remove(local_db)
- except OSError:
- logger.warning("Could not remove empty file %s"
- % local_db)
- return secrets, local_db
-
# initialization
def load_and_sync_soledad(self):
@@ -163,7 +165,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
uuid = self.srpauth.get_uid()
token = self.srpauth.get_token()
- secrets_path, local_db_path = self._get_db_paths(uuid)
+ secrets_path, local_db_path = get_db_paths(uuid)
# TODO: Select server based on timezone (issue #3308)
server_dict = self._soledad_config.get_hosts()
@@ -300,6 +302,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
except SSLError as exc:
logger.error("%r" % (exc,))
raise SoledadSyncError("Failed to sync soledad")
+ except u1db_errors.InvalidGeneration as exc:
+ logger.error("%r" % (exc,))
+ raise SoledadSyncError("u1db: InvalidGeneration")
+
except Exception as exc:
logger.exception("Unhandled error while syncing "
"soledad: %r" % (exc,))