summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/services/mail
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/services/mail')
-rw-r--r--src/leap/bitmask/services/mail/conductor.py30
-rw-r--r--src/leap/bitmask/services/mail/connection.py2
-rw-r--r--src/leap/bitmask/services/mail/plumber.py (renamed from src/leap/bitmask/services/mail/repair.py)205
3 files changed, 183 insertions, 54 deletions
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index addf9bef..79f324dc 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -35,6 +35,7 @@ from leap.common.check import leap_assert
from leap.common.events import register as leap_register
from leap.common.events import events_pb2 as leap_events
+
logger = logging.getLogger(__name__)
@@ -72,6 +73,8 @@ class IMAPControl(object):
"""
Starts imap service.
"""
+ from leap.bitmask.config import flags
+
logger.debug('Starting imap service')
leap_assert(sameProxiedObjects(self._soledad, None)
is not True,
@@ -81,16 +84,25 @@ class IMAPControl(object):
"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)
- self.imap_service.start_loop()
+ userid=self.userid,
+ offline=offline)
+
+ if offline is False:
+ logger.debug("Starting loop")
+ self.imap_service.start_loop()
- def stop_imap_service(self):
+ def stop_imap_service(self, cv):
"""
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
"""
self.imap_connection.qtsigs.disconnecting_signal.emit()
# TODO We should homogenize both services.
@@ -102,7 +114,14 @@ class IMAPControl(object):
# Stop listening on the IMAP port
self.imap_port.stopListening()
# Stop the protocol
- self.imap_factory.doStop()
+ 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):
"""
@@ -339,7 +358,7 @@ class MailConductor(IMAPControl, SMTPControl):
self._mail_machine = None
self._mail_connection = mail_connection.MailConnection()
- self.userid = None
+ self._userid = None
@property
def userid(self):
@@ -388,3 +407,4 @@ class MailConductor(IMAPControl, SMTPControl):
qtsigs.connecting_signal.connect(widget.mail_state_connecting)
qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting)
qtsigs.disconnected_signal.connect(widget.mail_state_disconnected)
+ qtsigs.soledad_invalid_auth_token.connect(widget.soledad_invalid_auth_token)
diff --git a/src/leap/bitmask/services/mail/connection.py b/src/leap/bitmask/services/mail/connection.py
index 29378f62..fdc28fe4 100644
--- a/src/leap/bitmask/services/mail/connection.py
+++ b/src/leap/bitmask/services/mail/connection.py
@@ -93,6 +93,8 @@ class MailConnectionSignals(QtCore.QObject):
connection_died_signal = QtCore.Signal()
connection_aborted_signal = QtCore.Signal()
+ soledad_invalid_auth_token = QtCore.Signal()
+
class MailConnection(AbstractLEAPConnection):
diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/plumber.py
index 767df1ef..c16a1fed 100644
--- a/src/leap/bitmask/services/mail/repair.py
+++ b/src/leap/bitmask/services/mail/plumber.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# repair.py
-# Copyright (C) 2013 LEAP
+# plumber.py
+# Copyright (C) 2013, 2014 LEAP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,20 +15,26 @@
# 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.
+Utils for manipulating local mailboxes.
"""
-import logging
import getpass
+import logging
import os
from collections import defaultdict
+from functools import partial
+from twisted.internet import defer
+
+from leap.bitmask.config.leapsettings import LeapSettings
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.provider import get_provider_path
from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths
+from leap.bitmask.util import flatten, get_path_prefix
-from leap.mail.imap.server import SoledadBackedAccount
+from leap.mail.imap.account import SoledadBackedAccount
+from leap.mail.imap.memorystore import MemoryStore
+from leap.mail.imap.soledadstore import SoledadStore
from leap.soledad.client import Soledad
logger = logging.getLogger(__name__)
@@ -89,69 +95,77 @@ class MBOXPlumber(object):
that can be invoked when data migration in the client is needed.
"""
- def __init__(self, userid, passwd):
+ def __init__(self, userid, passwd, mdir=None):
"""
- Initializes the plumber with all that's needed to authenticate
+ Initialize 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
+ :param mdir: a path to a maildir to import
+ :type mdir: str or None
"""
self.userid = userid
self.passwd = passwd
user, provider = userid.split('@')
self.user = user
+ self.mdir = mdir
self.sol = None
- provider_config_path = os.path.join(
- get_path_prefix(),
- "leap", "providers",
- provider, "provider.json")
+ self._settings = LeapSettings()
+
+ provider_config_path = os.path.join(get_path_prefix(),
+ get_provider_path(provider))
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):
+ def _init_local_soledad(self):
"""
- returns the user identifier for a given provider.
-
- :param provider: the provider to which we authenticate against.
+ Initialize local Soledad instance.
"""
- print "Authenticating with provider..."
- self.d = self.srp.authenticate(self.user, self.passwd)
+ self.uuid = self._settings.get_uuid(self.userid)
+ if not self.uuid:
+ print "Cannot get UUID from settings. Log in at least once."
+ return False
- 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)
+ print "UUID: %s" % (self.uuid)
- secrets, localdb = get_db_paths(self.uid)
+ secrets, localdb = get_db_paths(self.uuid)
self.sol = initialize_soledad(
- self.uid, self.userid, self.passwd,
+ self.uuid, self.userid, self.passwd,
secrets, localdb, "/tmp", "/tmp")
+ memstore = MemoryStore(
+ permanent_store=SoledadStore(self.sol),
+ write_period=5)
+ self.acct = SoledadBackedAccount(self.userid, self.sol,
+ memstore=memstore)
+ return True
+
+ #
+ # Account repairing
+ #
+
+ def repair_account(self, *args):
+ """
+ Repair mbox uids for all mboxes in this account.
+ """
+ init = self._init_local_soledad()
+ if not init:
+ return self.exit()
- self.acct = SoledadBackedAccount(self.userid, self.sol)
for mbox_name in self.acct.mailboxes:
- self.repair_mbox(mbox_name)
+ self.repair_mbox_uids(mbox_name)
print "done."
self.exit()
- def repair_mbox(self, mbox_name):
+ def repair_mbox_uids(self, mbox_name):
"""
- Repairs indexes for a given mbox
+ Repair indexes for a given mbox.
:param mbox_name: mailbox to repair
:type mbox_name: basestring
@@ -164,19 +178,21 @@ class MBOXPlumber(object):
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())
+ uids_iter = mbox.messages.all_msg_iter()
dupes = self._has_dupes(uids_iter)
if last_ok and not dupes:
print "Mbox does not need repair."
return
+ # XXX CHANGE? ----
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)
+ if mindex != old_uid:
+ print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid)
old_last_uid = mbox.last_uid
mbox.last_uid = len_mbox
@@ -184,7 +200,7 @@ class MBOXPlumber(object):
def _has_dupes(self, sequence):
"""
- Returns True if the given sequence of ints has duplicates.
+ Return True if the given sequence of ints has duplicates.
:param sequence: a sequence of ints
:type sequence: sequence
@@ -197,12 +213,82 @@ class MBOXPlumber(object):
return True
return False
+ #
+ # Maildir import
+ #
+ def import_mail(self, mail_filename):
+ """
+ Import a single mail into a mailbox.
+
+ :param mbox: the Mailbox instance to save in.
+ :type mbox: SoledadMailbox
+ :param mail_filename: the filename to the mail file to save
+ :type mail_filename: basestring
+ :return: a deferred
+ """
+ def saved(_):
+ print "message added"
+
+ with open(mail_filename) as f:
+ mail_string = f.read()
+ #uid = self._mbox.getUIDNext()
+ #print "saving with UID: %s" % uid
+ d = self._mbox.messages.add_msg(
+ mail_string, notify_on_disk=True)
+ return d
+
+ def import_maildir(self, mbox_name="INBOX"):
+ """
+ Import all mails in a maildir.
+
+ We will process all subfolders as beloging
+ to the same mailbox (cur, new, tmp).
+ """
+ # TODO parse hierarchical subfolders into
+ # inferior mailboxes.
+
+ if not os.path.isdir(self.mdir):
+ print "ERROR: maildir path does not exist."
+ return
+
+ init = self._init_local_soledad()
+ if not init:
+ return self.exit()
+
+ mbox = self.acct.getMailbox(mbox_name)
+ self._mbox = mbox
+ len_mbox = mbox.getMessageCount()
+
+ mail_files_g = flatten(
+ map(partial(os.path.join, f), files)
+ for f, _, files in os.walk(self.mdir))
+
+ # we only coerce the generator to give the
+ # len, but we could skip than and inform at the end.
+ mail_files = list(mail_files_g)
+ print "Got %s mails to import into %s (%s)" % (
+ len(mail_files), mbox_name, len_mbox)
+
+ def all_saved(_):
+ print "all messages imported"
+
+ deferreds = []
+ for f_name in mail_files:
+ deferreds.append(self.import_mail(f_name))
+ print "deferreds: ", deferreds
+
+ d1 = defer.gatherResults(deferreds, consumeErrors=False)
+ d1.addCallback(all_saved)
+ d1.addCallback(self._cbExit)
+
+ def _cbExit(self, ignored):
+ return self.exit()
+
def exit(self):
from twisted.internet import reactor
- self.d.cancel()
- if self.sol:
- self.sol.close()
try:
+ if self.sol:
+ self.sol.close()
reactor.stop()
except Exception:
pass
@@ -211,7 +297,8 @@ class MBOXPlumber(object):
def repair_account(userid):
"""
- Starts repair process for a given account.
+ Start repair process for a given account.
+
:param userid: the user id (email-like)
"""
from twisted.internet import reactor
@@ -219,7 +306,22 @@ def repair_account(userid):
# go mario!
plumber = MBOXPlumber(userid, passwd)
- reactor.callLater(1, plumber.start_auth)
+ reactor.callLater(1, plumber.repair_account)
+ reactor.run()
+
+
+def import_maildir(userid, maildir_path):
+ """
+ Start import-maildir 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, mdir=maildir_path)
+ reactor.callLater(1, plumber.import_maildir)
reactor.run()
@@ -228,7 +330,12 @@ if __name__ == "__main__":
logging.basicConfig()
- if len(sys.argv) != 2:
- print "Usage: repair <username>"
+ if len(sys.argv) != 3:
+ print "Usage: plumber [repair|import] <username>"
sys.exit(1)
- repair_account(sys.argv[1])
+
+ # this would be better with a dict if it grows
+ if sys.argv[1] == "repair":
+ repair_account(sys.argv[2])
+ if sys.argv[1] == "import":
+ print "Not implemented yet."