Unverified Commit c6db8a1f authored by Kali Kaneko's avatar Kali Kaneko
Browse files

some fixes after review

parent bfd8886e
......@@ -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)
......@@ -84,6 +84,7 @@ class CommandDispatcher(object):
return d
def do_EIP(self, *parts):
subcmd = parts[1]
eip_label = 'eip'
......
# -*- 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)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment