diff options
-rw-r--r-- | src/leap/bitmask/core/configurable.py | 174 | ||||
-rw-r--r-- | src/leap/bitmask/core/dispatcher.py | 1 | ||||
-rw-r--r-- | src/leap/bitmask/core/mail_services.py | 64 |
3 files changed, 52 insertions, 187 deletions
diff --git a/src/leap/bitmask/core/configurable.py b/src/leap/bitmask/core/configurable.py index 3b97916d..8e33de95 100644 --- a/src/leap/bitmask/core/configurable.py +++ b/src/leap/bitmask/core/configurable.py @@ -18,15 +18,15 @@ Configurable Backend for Bitmask Service. """ import ConfigParser -import locale import os -import re -import sys from twisted.application import service -from twisted.python import log from leap.common import files +from leap.common.config import get_path_prefix + + +DEFAULT_BASEDIR = os.path.join(get_path_prefix(), 'leap') class MissingConfigEntry(Exception): @@ -40,7 +40,7 @@ class ConfigurableService(service.MultiService): config_file = u"bitmaskd.cfg" service_names = ('mail', 'eip', 'zmq', 'web') - def __init__(self, basedir='~/.config/leap'): + def __init__(self, basedir=DEFAULT_BASEDIR): service.MultiService.__init__(self) path = os.path.abspath(os.path.expanduser(basedir)) @@ -61,10 +61,9 @@ class ConfigurableService(service.MultiService): except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): if default is None: - fn = os.path.join(self.basedir, self.config_file) + fn = self._get_config_path() raise MissingConfigEntry("%s is missing the [%s]%s entry" - % (_quote_output(fn), - section, option)) + % fn, section, option) return default def set_config(self, section, option, value): @@ -82,12 +81,8 @@ class ConfigurableService(service.MultiService): if not os.path.isfile(bitmaskd_cfg): self._create_default_config(bitmaskd_cfg) - try: - with open(bitmaskd_cfg, "rb") as f: - self.config.readfp(f) - except EnvironmentError: - if os.path.exists(bitmaskd_cfg): - raise + with open(bitmaskd_cfg, "rb") as f: + self.config.readfp(f) def save_config(self): bitmaskd_cfg = self._get_config_path() @@ -109,154 +104,3 @@ eip = True zmq = True web = False """ - - -def canonical_encoding(encoding): - if encoding is None: - log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD) - encoding = 'utf-8' - encoding = encoding.lower() - if encoding == "cp65001": - encoding = 'utf-8' - elif (encoding == "us-ascii" or encoding == "646" or encoding == - "ansi_x3.4-1968"): - encoding = 'ascii' - - return encoding - - -def check_encoding(encoding): - # sometimes Python returns an encoding name that it doesn't support for - # conversion fail early if this happens - try: - u"test".encode(encoding) - except (LookupError, AttributeError): - raise AssertionError( - "The character encoding '%s' is not supported for conversion." % ( - encoding,)) - -filesystem_encoding = None -io_encoding = None -is_unicode_platform = False - - -def _reload(): - global filesystem_encoding, io_encoding, is_unicode_platform - - filesystem_encoding = canonical_encoding(sys.getfilesystemencoding()) - check_encoding(filesystem_encoding) - - if sys.platform == 'win32': - # On Windows we install UTF-8 stream wrappers for sys.stdout and - # sys.stderr, and reencode the arguments as UTF-8 (see - # scripts/runner.py). - io_encoding = 'utf-8' - else: - ioenc = None - if hasattr(sys.stdout, 'encoding'): - ioenc = sys.stdout.encoding - if ioenc is None: - try: - ioenc = locale.getpreferredencoding() - except Exception: - pass # work around <http://bugs.python.org/issue1443504> - io_encoding = canonical_encoding(ioenc) - - check_encoding(io_encoding) - - is_unicode_platform = sys.platform in ["win32", "darwin"] - -_reload() - - -def _quote_output(s, quotemarks=True, quote_newlines=None, encoding=None): - """ - Encode either a Unicode string or a UTF-8-encoded bytestring for - representation on stdout or stderr, tolerating errors. If 'quotemarks' is - True, the string is always quoted; otherwise, it is quoted only if - necessary to avoid ambiguity or control bytes in the output. (Newlines are - counted as control bytes iff quote_newlines is True.) - - Quoting may use either single or double quotes. Within single quotes, all - characters stand for themselves, and ' will not appear. Within double - quotes, Python-compatible backslash escaping is used. - - If not explicitly given, quote_newlines is True when quotemarks is True. - """ - assert isinstance(s, (str, unicode)) - if quote_newlines is None: - quote_newlines = quotemarks - - if isinstance(s, str): - try: - s = s.decode('utf-8') - except UnicodeDecodeError: - return 'b"%s"' % ( - ESCAPABLE_8BIT.sub( - lambda m: _str_escape(m, quote_newlines), s),) - - must_double_quote = (quote_newlines and MUST_DOUBLE_QUOTE_NL or - MUST_DOUBLE_QUOTE) - if must_double_quote.search(s) is None: - try: - out = s.encode(encoding or io_encoding) - if quotemarks or out.startswith('"'): - return "'%s'" % (out,) - else: - return out - except (UnicodeDecodeError, UnicodeEncodeError): - pass - - escaped = ESCAPABLE_UNICODE.sub( - lambda m: _unicode_escape(m, quote_newlines), s) - return '"%s"' % ( - escaped.encode(encoding or io_encoding, 'backslashreplace'),) - - -def _unicode_escape(m, quote_newlines): - u = m.group(0) - if u == u'"' or u == u'$' or u == u'`' or u == u'\\': - return u'\\' + u - elif u == u'\n' and not quote_newlines: - return u - if len(u) == 2: - codepoint = ( - ord(u[0]) - 0xD800) * 0x400 + ord(u[1]) - 0xDC00 + 0x10000 - else: - codepoint = ord(u) - if codepoint > 0xFFFF: - return u'\\U%08x' % (codepoint,) - elif codepoint > 0xFF: - return u'\\u%04x' % (codepoint,) - else: - return u'\\x%02x' % (codepoint,) - - -def _str_escape(m, quote_newlines): - c = m.group(0) - if c == '"' or c == '$' or c == '`' or c == '\\': - return '\\' + c - elif c == '\n' and not quote_newlines: - return c - else: - return '\\x%02x' % (ord(c),) - -MUST_DOUBLE_QUOTE_NL = re.compile( - ur'[^\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', - re.DOTALL) -MUST_DOUBLE_QUOTE = re.compile( - ur'[^\n\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', - re.DOTALL) - -ESCAPABLE_8BIT = re.compile( - r'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E]', - re.DOTALL) - -# if we must double-quote, then we have to escape ", $ and `, but need not -# escape ' - -ESCAPABLE_UNICODE = re.compile( - ur'([\uD800-\uDBFF][\uDC00-\uDFFF])|' # valid surrogate pairs - ur'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E\u00A0-\uD7FF' - ur'\uE000-\uFDCF\uFDF0-\uFFFC]', - re.DOTALL) diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 4d7e1813..648cbe9b 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -84,6 +84,7 @@ class CommandDispatcher(object): return d def do_EIP(self, *parts): + subcmd = parts[1] eip_label = 'eip' diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index a0be1211..6472bdc2 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -1,3 +1,19 @@ +# -*- coding: utf-8 -*- +# mail_services.py +# Copyright (C) 2016 LEAP Encryption Acess Project +# +# 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/>. """ Mail services. @@ -6,7 +22,6 @@ This should be moved to the different packages when it stabilizes. """ import json import os -from glob import glob from collections import defaultdict from collections import namedtuple @@ -26,31 +41,30 @@ from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD from leap.mail import smtp from leap.bitmask.core.uuid_map import UserMap +from leap.bitmask.core.configurable import DEFAULT_BASEDIR class Container(object): - def __init__(self): + def __init__(self, service=None): self._instances = defaultdict(None) + if service is not None: + self.service = service def get_instance(self, key): return self._instances.get(key, None) + def add_instance(self, key, data): + self._instances[key] = data + class ImproperlyConfigured(Exception): pass -def get_all_soledad_uuids(): - return [os.path.split(p)[-1].split('.db')[0] for p in - glob(os.path.expanduser('~/.config/leap/soledad/*.db'))] - # FIXME do not hardcode basedir - - class SoledadContainer(Container): - def __init__(self, basedir='~/.config/leap'): - # FIXME do not hardcode basedir + def __init__(self, basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) self._usermap = UserMap() super(SoledadContainer, self).__init__() @@ -75,19 +89,17 @@ class SoledadContainer(Container): uuid, passphrase, soledad_path, soledad_url, cert_path, token) - self._instances[userid] = soledad + self.add_instances(userid, soledad) data = {'user': userid, 'uuid': uuid, 'token': token, 'soledad': soledad} self.service.trigger_hook('on_new_soledad_instance', **data) - def _create_soledad_instance(self, uuid, passphrase, basedir, server_url, - cert_file, token): + def _create_soledad_instance(self, uuid, passphrase, soledad_path, + server_url, cert_file, token): # setup soledad info - secrets_path = os.path.join( - basedir, '%s.secret' % uuid) - local_db_path = os.path.join( - basedir, '%s.db' % uuid) + secrets_path = os.path.join(soledad_path, '%s.secret' % uuid) + local_db_path = os.path.join(soledad_path, '%s.db' % uuid) if token is None: syncable = False @@ -143,8 +155,7 @@ class SoledadService(HookableService): def startService(self): log.msg('Starting Soledad Service') - self._container = SoledadContainer() - self._container.service = self + self._container = SoledadContainer(service=self) super(SoledadService, self).startService() # hooks @@ -209,7 +220,7 @@ class KeymanagerContainer(Container): def _on_keymanager_ready_cb(self, keymanager, userid, soledad): # TODO use onready-deferreds instead - self._instances[userid] = keymanager + self.add_instance(userid, keymanager) log.msg("Adding Keymanager instance for: %s" % userid) data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager} @@ -264,6 +275,11 @@ class KeymanagerContainer(Container): token = self.service.tokens.get(userid) km_args = (userid, nickserver_uri, soledad) + + # TODO use the method in + # services.soledadbootstrapper._get_gpg_bin_path. + # That should probably live in keymanager package. + km_kwargs = { "token": token, "uid": uuid, "api_uri": api_uri, "api_version": "1", @@ -373,10 +389,12 @@ class StandardMailService(service.MultiService, HookableService): def initializeChildrenServices(self): self.addService(IMAPService(self._soledad_sessions)) - self.addService(IncomingMailService(self)) self.addService(SMTPService( self._soledad_sessions, self._keymanager_sessions, self._sendmail_opts)) + # TODO adapt the service to receive soledad/keymanager sessions object. + # See also the TODO before IncomingMailService.startInstance + self.addService(IncomingMailService(self)) def startService(self): log.msg('Starting Mail Service...') @@ -438,6 +456,7 @@ class StandardMailService(service.MultiService, HookableService): if not active_user: return defer.succeed('NO ACTIVE USER') token = self._imap_tokens.get(active_user) + # TODO return just the tuple, no format. return defer.succeed("IMAP TOKEN (%s): %s" % (active_user, token)) def get_smtp_token(self): @@ -445,6 +464,7 @@ class StandardMailService(service.MultiService, HookableService): if not active_user: return defer.succeed('NO ACTIVE USER') token = self._smtp_tokens.get(active_user) + # TODO return just the tuple, no format. return defer.succeed("SMTP TOKEN (%s): %s" % (active_user, token)) def do_get_smtp_cert_path(self, userid): @@ -560,7 +580,7 @@ class IncomingMailService(service.Service): if start_sync: incoming_instance.startService() - acc = Account(soledad) + acc = Account(soledad, userid) d = acc.callWhenReady( lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) |