From 8fdaf603e313b9cb938f2f41713862e75f40d583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 21 Oct 2013 10:18:54 -0300 Subject: Default to UTF-8 when there is not charset parsed from the mail contents --- changes/bug_default_to_utf8 | 2 ++ src/leap/mail/imap/server.py | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 changes/bug_default_to_utf8 diff --git a/changes/bug_default_to_utf8 b/changes/bug_default_to_utf8 new file mode 100644 index 0000000..898138b --- /dev/null +++ b/changes/bug_default_to_utf8 @@ -0,0 +1,2 @@ + o Default to UTF-8 when there is no charset parsed from the mail + contents. \ No newline at end of file diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 10d338a..df510ce 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -711,6 +711,8 @@ class LeapMessage(WithMsgFields): # Miniparser for: Content-Type: ; charset= charset_re = r'''charset=(?P[\w|\d|-]*)''' charset = re.findall(charset_re, em["Content-Type"])[0] + if charset is None or len(charset) == 0: + charset = "UTF-8" except Exception: pass return charset -- cgit v1.2.3 From c0834048d564dfdc3e2cedc1a0f81788d14f3cab Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:17:21 -0300 Subject: Move charset parser to a utils module. --- src/leap/mail/imap/server.py | 29 +++-------------------------- src/leap/mail/utils.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 src/leap/mail/utils.py diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index df510ce..b9a041d 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -18,9 +18,7 @@ Soledad-backed IMAP Server. """ import copy -import email import logging -import re import StringIO import cStringIO import time @@ -41,6 +39,7 @@ from leap.common import events as leap_events from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL from leap.common.check import leap_assert, leap_assert_type from leap.soledad.client import Soledad +from leap.mail.utils import get_email_charset logger = logging.getLogger(__name__) @@ -695,28 +694,6 @@ class LeapMessage(WithMsgFields): the more complex MIME-based interface. """ - def _get_charset(self, content): - """ - Mini parser to retrieve the charset of an email - - :param content: mail contents - :type content: unicode - - :returns: the charset as parsed from the contents - :rtype: str - """ - charset = "UTF-8" - try: - em = email.message_from_string(content.encode("utf-8")) - # Miniparser for: Content-Type: ; charset= - charset_re = r'''charset=(?P[\w|\d|-]*)''' - charset = re.findall(charset_re, em["Content-Type"])[0] - if charset is None or len(charset) == 0: - charset = "UTF-8" - except Exception: - pass - return charset - def open(self): """ Return an file-like object opened for reading. @@ -728,7 +705,7 @@ class LeapMessage(WithMsgFields): :rtype: StringIO """ fd = cStringIO.StringIO() - charset = self._get_charset(self._doc.content.get(self.RAW_KEY, '')) + charset = get_email_charset(self._doc.content.get(self.RAW_KEY, '')) fd.write(self._doc.content.get(self.RAW_KEY, '').encode(charset)) fd.seek(0) return fd @@ -748,7 +725,7 @@ class LeapMessage(WithMsgFields): :rtype: StringIO """ fd = StringIO.StringIO() - charset = self._get_charset(self._doc.content.get(self.RAW_KEY, '')) + charset = get_email_charset(self._doc.content.get(self.RAW_KEY, '')) fd.write(self._doc.content.get(self.RAW_KEY, '').encode(charset)) # SHOULD use a separate BODY FIELD ... fd.seek(0) diff --git a/src/leap/mail/utils.py b/src/leap/mail/utils.py new file mode 100644 index 0000000..22e16a7 --- /dev/null +++ b/src/leap/mail/utils.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# utils.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 . +""" +Utility functions for email. +""" +import email +import re + + +def get_email_charset(content): + """ + Mini parser to retrieve the charset of an email. + + :param content: mail contents + :type content: unicode + + :returns: the charset as parsed from the contents + :rtype: str + """ + charset = "UTF-8" + try: + em = email.message_from_string(content.encode("utf-8")) + # Miniparser for: Content-Type: ; charset= + charset_re = r'''charset=(?P[\w|\d|-]*)''' + charset = re.findall(charset_re, em["Content-Type"])[0] + if charset is None or len(charset) == 0: + charset = "UTF-8" + except Exception: + pass + return charset -- cgit v1.2.3 From f864f21b51b9f767afa8ccb3b3f39967b2f08f60 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:18:34 -0300 Subject: Add encoding exception catch to avoid crashes. --- src/leap/mail/imap/server.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index b9a041d..7ae3c45 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -706,7 +706,13 @@ class LeapMessage(WithMsgFields): """ fd = cStringIO.StringIO() charset = get_email_charset(self._doc.content.get(self.RAW_KEY, '')) - fd.write(self._doc.content.get(self.RAW_KEY, '').encode(charset)) + content = self._doc.content.get(self.RAW_KEY, '') + try: + content = content.encode(charset) + except (UnicodeEncodeError, UnicodeDecodeError) as e: + logger.error("Unicode error {0}".format(e)) + content = content.encode(charset, 'replace') + fd.write(content) fd.seek(0) return fd @@ -726,7 +732,13 @@ class LeapMessage(WithMsgFields): """ fd = StringIO.StringIO() charset = get_email_charset(self._doc.content.get(self.RAW_KEY, '')) - fd.write(self._doc.content.get(self.RAW_KEY, '').encode(charset)) + content = self._doc.content.get(self.RAW_KEY, '') + try: + content = content.encode(charset) + except (UnicodeEncodeError, UnicodeDecodeError) as e: + logger.error("Unicode error {0}".format(e)) + content = content.encode(charset, 'replace') + fd.write(content) # SHOULD use a separate BODY FIELD ... fd.seek(0) return fd -- cgit v1.2.3 From 7dd2e6ed4d1980626f486a6d5065a4fd5ffdfeb3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:19:44 -0300 Subject: Use correct encoding and data type in mails. --- src/leap/mail/imap/fetch.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/leap/mail/imap/fetch.py b/src/leap/mail/imap/fetch.py index 0a71f53..a776ac7 100644 --- a/src/leap/mail/imap/fetch.py +++ b/src/leap/mail/imap/fetch.py @@ -40,6 +40,7 @@ from leap.common.events.events_pb2 import IMAP_MSG_DECRYPTED from leap.common.events.events_pb2 import IMAP_MSG_SAVED_LOCALLY from leap.common.events.events_pb2 import IMAP_MSG_DELETED_INCOMING from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL +from leap.mail.utils import get_email_charset logger = logging.getLogger(__name__) @@ -296,12 +297,17 @@ class LeapIncomingMail(object): Tries to decrypt a gpg message if data looks like one. :param data: the text to be decrypted. - :type data: str + :type data: unicode :return: data, possibly descrypted. :rtype: str """ + leap_assert_type(data, unicode) + parser = Parser() + encoding = get_email_charset(data) + data = data.encode(encoding) origmsg = parser.parsestr(data) + # handle multipart/encrypted messages if origmsg.get_content_type() == 'multipart/encrypted': # sanity check @@ -320,13 +326,21 @@ class LeapIncomingMail(object): "Multipart/encrypted messages' second body part should " "have content type equal to 'octet-stream' (instead of " "%s)." % payload[1].get_content_type()) + # parse message and get encrypted content pgpencmsg = origmsg.get_payload()[1] encdata = pgpencmsg.get_payload() + # decrypt and parse decrypted message decrdata = self._keymanager.decrypt( encdata, self._pkey, passphrase=self._soledad.passphrase) + try: + decrdata = decrdata.encode(encoding) + except (UnicodeEncodeError, UnicodeDecodeError) as e: + logger.error("Unicode error {0}".format(e)) + decrdata = decrdata.encode(encoding, 'replace') + decrmsg = parser.parsestr(decrdata) # replace headers back in original message for hkey, hval in decrmsg.items(): @@ -335,6 +349,7 @@ class LeapIncomingMail(object): origmsg.replace_header(hkey, hval) except KeyError: origmsg[hkey] = hval + # replace payload by unencrypted payload origmsg.set_payload(decrmsg.get_payload()) return origmsg.as_string(unixfrom=False) @@ -352,6 +367,10 @@ class LeapIncomingMail(object): # replace encrypted by decrypted content data = data.replace(pgp_message, decrdata) # if message is not encrypted, return raw data + + if isinstance(data, unicode): + data = data.encode(encoding, 'replace') + return data def _add_message_locally(self, msgtuple): -- cgit v1.2.3 From 2e5dbaec5189603615c7b4e9e93bd6129af9c3b1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:22:47 -0300 Subject: Remove commented imports. --- src/leap/mail/imap/server.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 7ae3c45..6fc4db3 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -31,10 +31,6 @@ from twisted.mail import imap4 from twisted.internet import defer from twisted.python import log -#from twisted import cred - -#import u1db - from leap.common import events as leap_events from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL from leap.common.check import leap_assert, leap_assert_type -- cgit v1.2.3 From 755d4a18ce80db745118edb9b2b27d359ed839d2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:24:39 -0300 Subject: pep8 fix: line too long. --- src/leap/mail/imap/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 6fc4db3..5a98315 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -179,7 +179,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): # messages TYPE_MBOX_SEEN_IDX: [KTYPE, MBOX_VAL, 'bool(seen)'], TYPE_MBOX_RECT_IDX: [KTYPE, MBOX_VAL, 'bool(recent)'], - TYPE_MBOX_RECT_SEEN_IDX: [KTYPE, MBOX_VAL, 'bool(recent)', 'bool(seen)'], + TYPE_MBOX_RECT_SEEN_IDX: [KTYPE, MBOX_VAL, + 'bool(recent)', 'bool(seen)'], } INBOX_NAME = "INBOX" -- cgit v1.2.3 From bea7ee293c720b8b632a3b5149e4550c6409fd20 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 11:33:19 -0300 Subject: Add changelog file for #4000. --- changes/bug-4000_support-non-ascii | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug-4000_support-non-ascii diff --git a/changes/bug-4000_support-non-ascii b/changes/bug-4000_support-non-ascii new file mode 100644 index 0000000..8f6712d --- /dev/null +++ b/changes/bug-4000_support-non-ascii @@ -0,0 +1 @@ + o Add support for non-ascii characters in emails. Closes #4000. -- cgit v1.2.3 From d646db5fc8b6f79a66bc96280f59bc90afec4e8b Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 24 Oct 2013 16:25:51 -0200 Subject: Fix tests according to rfc 3156. Also fix test suite loading. --- setup.py | 2 +- src/leap/mail/__init__.py | 9 +---- src/leap/mail/load_tests.py | 32 ++++++++++++++++ src/leap/mail/smtp/tests/test_smtprelay.py | 59 +++++++++++++++++++++++------- 4 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 src/leap/mail/load_tests.py diff --git a/setup.py b/setup.py index f423f7b..57a4164 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ setup( namespace_packages=["leap"], package_dir={'': 'src'}, packages=find_packages('src'), - test_suite='leap.mail.load_tests', + test_suite='leap.mail.load_tests.load_tests', install_requires=utils.parse_requirements(), tests_require=utils.parse_requirements( reqfiles=['pkg/requirements-testing.pip']), diff --git a/src/leap/mail/__init__.py b/src/leap/mail/__init__.py index 5b5ba9b..4b25fe6 100644 --- a/src/leap/mail/__init__.py +++ b/src/leap/mail/__init__.py @@ -17,17 +17,10 @@ """ -Provide function for loading tests. +Client mail bits. """ -# Do not force the unittest dependency -# import unittest - - -# def load_tests(): -# return unittest.defaultTestLoader.discover('./src/leap/mail') - from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/src/leap/mail/load_tests.py b/src/leap/mail/load_tests.py new file mode 100644 index 0000000..ee89fcc --- /dev/null +++ b/src/leap/mail/load_tests.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# tests.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 . + + +""" +Provide a function for loading tests. +""" + +import unittest + + +def load_tests(): + suite = unittest.TestSuite() + for test in unittest.defaultTestLoader.discover( + './src/leap/mail/', + top_level_dir='./src/'): + suite.addTest(test) + return suite diff --git a/src/leap/mail/smtp/tests/test_smtprelay.py b/src/leap/mail/smtp/tests/test_smtprelay.py index a529c93..7fefe77 100644 --- a/src/leap/mail/smtp/tests/test_smtprelay.py +++ b/src/leap/mail/smtp/tests/test_smtprelay.py @@ -23,8 +23,8 @@ SMTP relay tests. import re - from datetime import datetime +from gnupg._util import _make_binary_stream from twisted.test import proto_helpers from twisted.mail.smtp import ( User, @@ -33,7 +33,6 @@ from twisted.mail.smtp import ( ) from mock import Mock - from leap.mail.smtp.smtprelay import ( SMTPFactory, EncryptedMessage, @@ -45,7 +44,6 @@ from leap.mail.smtp.tests import ( ) from leap.keymanager import openpgp - # some regexps IP_REGEX = "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}" + \ "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" @@ -127,11 +125,22 @@ class TestSmtpRelay(TestCaseWithKeyManager): for line in self.EMAIL_DATA[4:12]: m.lineReceived(line) m.eomReceived() + # assert structure of encrypted message + self.assertTrue('Content-Type' in m._msg) + self.assertEqual('multipart/encrypted', m._msg.get_content_type()) + self.assertEqual('application/pgp-encrypted', + m._msg.get_param('protocol')) + self.assertEqual(2, len(m._msg.get_payload())) + self.assertEqual('application/pgp-encrypted', + m._msg.get_payload(0).get_content_type()) + self.assertEqual('application/octet-stream', + m._msg.get_payload(1).get_content_type()) privkey = self._km.get_key( ADDRESS, openpgp.OpenPGPKey, private=True) - decrypted = self._km.decrypt(m._message.get_payload(), privkey) + decrypted = self._km.decrypt( + m._msg.get_payload(1).get_payload(), privkey) self.assertEqual( - '\r\n'.join(self.EMAIL_DATA[9:12]) + '\r\n', + '\n' + '\r\n'.join(self.EMAIL_DATA[9:12]) + '\r\n', decrypted, 'Decrypted text differs from plaintext.') @@ -149,14 +158,24 @@ class TestSmtpRelay(TestCaseWithKeyManager): m.lineReceived(line) # trigger encryption and signing m.eomReceived() + # assert structure of encrypted message + self.assertTrue('Content-Type' in m._msg) + self.assertEqual('multipart/encrypted', m._msg.get_content_type()) + self.assertEqual('application/pgp-encrypted', + m._msg.get_param('protocol')) + self.assertEqual(2, len(m._msg.get_payload())) + self.assertEqual('application/pgp-encrypted', + m._msg.get_payload(0).get_content_type()) + self.assertEqual('application/octet-stream', + m._msg.get_payload(1).get_content_type()) # decrypt and verify privkey = self._km.get_key( ADDRESS, openpgp.OpenPGPKey, private=True) pubkey = self._km.get_key(ADDRESS_2, openpgp.OpenPGPKey) decrypted = self._km.decrypt( - m._message.get_payload(), privkey, verify=pubkey) + m._msg.get_payload(1).get_payload(), privkey, verify=pubkey) self.assertEqual( - '\r\n'.join(self.EMAIL_DATA[9:12]) + '\r\n', + '\n' + '\r\n'.join(self.EMAIL_DATA[9:12]) + '\r\n', decrypted, 'Decrypted text differs from plaintext.') @@ -175,22 +194,34 @@ class TestSmtpRelay(TestCaseWithKeyManager): m.lineReceived(line) # trigger signing m.eomReceived() + # assert structure of signed message + self.assertTrue('Content-Type' in m._msg) + self.assertEqual('multipart/signed', m._msg.get_content_type()) + self.assertEqual('application/pgp-signature', + m._msg.get_param('protocol')) + self.assertEqual('pgp-sha512', m._msg.get_param('micalg')) # assert content of message + self.assertEqual( + m._msg.get_payload(0).get_payload(decode=True), + '\r\n'.join(self.EMAIL_DATA[9:13])) + # assert content of signature self.assertTrue( - m._message.get_payload().startswith( - '-----BEGIN PGP SIGNED MESSAGE-----\n' + - 'Hash: SHA1\n\n' + - ('\r\n'.join(self.EMAIL_DATA[9:12]) + '\r\n' + - '-----BEGIN PGP SIGNATURE-----\n')), + m._msg.get_payload(1).get_payload().startswith( + '-----BEGIN PGP SIGNATURE-----\n'), 'Message does not start with signature header.') self.assertTrue( - m._message.get_payload().endswith( + m._msg.get_payload(1).get_payload().endswith( '-----END PGP SIGNATURE-----\n'), 'Message does not end with signature footer.') # assert signature is valid pubkey = self._km.get_key(ADDRESS_2, openpgp.OpenPGPKey) + # replace EOL before verifying (according to rfc3156) + signed_text = re.sub('\r?\n', '\r\n', + m._msg.get_payload(0).as_string()) self.assertTrue( - self._km.verify(m._message.get_payload(), pubkey), + self._km.verify(signed_text, + pubkey, + detached_sig=m._msg.get_payload(1).get_payload()), 'Signature could not be verified.') def test_missing_key_rejects_address(self): -- cgit v1.2.3 From c5b8e13c6c016a880f88e95d3f084c12b94029c5 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 24 Oct 2013 16:26:40 -0200 Subject: Implement TLS wrapper mode. --- changes/feature_3637-use-tls-wrapper-mode | 1 + src/leap/mail/smtp/smtprelay.py | 31 ++++++++++++++----- src/leap/mail/smtp/tests/__init__.py | 4 +-- src/leap/mail/smtp/tests/cert/server.crt | 29 ++++++++++++++++++ src/leap/mail/smtp/tests/cert/server.key | 51 +++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 changes/feature_3637-use-tls-wrapper-mode create mode 100644 src/leap/mail/smtp/tests/cert/server.crt create mode 100644 src/leap/mail/smtp/tests/cert/server.key diff --git a/changes/feature_3637-use-tls-wrapper-mode b/changes/feature_3637-use-tls-wrapper-mode new file mode 100644 index 0000000..668015f --- /dev/null +++ b/changes/feature_3637-use-tls-wrapper-mode @@ -0,0 +1 @@ + o Use TLS wrapper mode instead of STARTTLS. Closes #3637. diff --git a/src/leap/mail/smtp/smtprelay.py b/src/leap/mail/smtp/smtprelay.py index d9bbbf9..fca66c0 100644 --- a/src/leap/mail/smtp/smtprelay.py +++ b/src/leap/mail/smtp/smtprelay.py @@ -17,6 +17,20 @@ """ LEAP SMTP encrypted relay. + +The following classes comprise the SMTP relay service: + + * SMTPFactory - A twisted.internet.protocol.ServerFactory that provides + the SMTPDelivery protocol. + * SMTPDelivery - A twisted.mail.smtp.IMessageDelivery implementation. It + knows how to validate sender and receiver of messages and it generates + an EncryptedMessage for each recipient. + * SSLContextFactory - Contains the relevant ssl information for the + connection. + * EncryptedMessage - An implementation of twisted.mail.smtp.IMessage that + knows how to encrypt/sign itself before sending. + + """ import re @@ -173,7 +187,6 @@ class SMTPFactory(ServerFactory): @return: The protocol. @rtype: SMTPDelivery """ - # If needed, we might use ESMTPDelivery here instead. smtpProtocol = smtp.SMTP(SMTPDelivery(self._km, self._config)) smtpProtocol.factory = self return smtpProtocol @@ -305,7 +318,7 @@ class SMTPDelivery(object): # EncryptedMessage # -class CtxFactory(ssl.ClientContextFactory): +class SSLContextFactory(ssl.ClientContextFactory): def __init__(self, cert, key): self.cert = cert self.key = key @@ -450,6 +463,8 @@ class EncryptedMessage(object): self._config[PORT_KEY])) d = defer.Deferred() + # we don't pass an ssl context factory to the ESMTPSenderFactory + # because ssl will be handled by reactor.connectSSL() below. factory = smtp.ESMTPSenderFactory( "", # username is blank because server does not use auth. "", # password is blank because server does not use auth. @@ -457,15 +472,15 @@ class EncryptedMessage(object): self._user.dest.addrstr, StringIO(msg), d, - contextFactory=CtxFactory(self._config[CERT_KEY], - self._config[KEY_KEY]), - requireAuthentication=False) + requireAuthentication=False, + requireTransportSecurity=True) signal(proto.SMTP_SEND_MESSAGE_START, self._user.dest.addrstr) - reactor.connectTCP( + reactor.connectSSL( self._config[HOST_KEY], self._config[PORT_KEY], - factory - ) + factory, + contextFactory=SSLContextFactory(self._config[CERT_KEY], + self._config[KEY_KEY])) d.addCallback(self.sendSuccess) d.addErrback(self.sendError) return d diff --git a/src/leap/mail/smtp/tests/__init__.py b/src/leap/mail/smtp/tests/__init__.py index 7fed7da..9b54de3 100644 --- a/src/leap/mail/smtp/tests/__init__.py +++ b/src/leap/mail/smtp/tests/__init__.py @@ -113,8 +113,8 @@ class TestCaseWithKeyManager(BaseLeapTest): 'username': address, 'password': '', 'encrypted_only': True, - 'cert': 'blah', - 'key': 'bleh', + 'cert': 'src/leap/mail/smtp/tests/cert/server.crt', + 'key': 'src/leap/mail/smtp/tests/cert/server.key', } class Response(object): diff --git a/src/leap/mail/smtp/tests/cert/server.crt b/src/leap/mail/smtp/tests/cert/server.crt new file mode 100644 index 0000000..a27391c --- /dev/null +++ b/src/leap/mail/smtp/tests/cert/server.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFBjCCAu4CCQCWn3oMoQrDJTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTEzMTAyMzE0NDUwNFoXDTE2MDcxOTE0NDUwNFowRTELMAkG +A1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +APexTvEvG7cSmZdAERHt9TB11cSor54Y/F7NmYMdSOJNi4Y0kwkSslpdfipi+mt/ +NFg/uGKi1mcgvuXdVbVPZ9rCgVpIzMncO8RAP7a5+I2zKUzqMCCbLH16sYpo/rDk +VQ5V15TwLsTzOFGG8Cgp68TR8zHuZ4Edf2zMGC1IaiJ6W38LTnJgsowYOCFDAF3z +L36kxMO5gNGEUYV6tjltx+rAcXka3po+xiAgvW6q65UUgDHcIdEGG2dc9bkxxPl7 +RkprF2RwwADNzYS7Tn+Hpmjy06pfYZHNME+Iw515bCRF3GQFUU4BpGnY7EO+h4P9 +Kb1h948gUT9/oswXG+q2Kwk8AoggMJkUOWDFiCa5UjW1GBoxxb7VtZ+QTJXxlFWc +M2VzT7M/HX+P4b05vY4MXJjxPAFKrAGS7J8DKW8WJNUnXa9XSDBHg5qijDzZ/zGm +HTdG6iADnJLmOHBQgFQ12a/n9mYV2GPVC6FlgDzG9f0/SUPBUCafyWYz1LwKY4VM +2NLx/iwYMQsNIMSZQfNmufNDBr70+BShe3ZpbmKB/J33d87AuJd2HjnsThTEAAr+ +6CejyYmwFutoDUCF8IaKGJEp7OGP2//ub4nt5WwW8DYLRi8EqtzEnxPo5ZiayHMY +GHR1jpX1O5JVJFUE79bZCFFHKmtJc4kVZS4m4rTLsk83AgMBAAEwDQYJKoZIhvcN +AQEFBQADggIBAEt4PIRqVuALQSdgZ+GiZYuvEVjxoDVtMSc/ym93Gi8R7DDivFH9 +4suQc5QUiuEF8lpEtkmh+PZ+oFdQkjhBH80h7p4BUSyBy5Yi6dy7ATTlBAqwzCYZ +4wzHeJzu1SI6FinZLksoULbcw04n410aGHkLa6I9O3vCC4kXSnBlwU1sUsJphxM2 +3pkHBpvv79XYf5kFqZPzF16aO7rxFuVvqgXLyzwuyP9kH5zMA21Kioxs/pNyg1lm +5h0VinpHLPse+4tYih1L1WLMpEZiSwZgFhoRtlcdIVXokZPaX4G2EkdrMmSQruWg +Uz8Av6LEYHmRfbYwYM2kEX/+AF8thpTQDbvxjqYk5oyGX4wpKGpih1ac/jYu3O8B +VLhbxZlBYcLxCqqNsGJrWaiHj2Jf4GhUB0O9hXfaZDMqEGXT9GzOz0yF6b3pDQVy +H0lKIBb+kQzB/jhZKu4vrTAowXtt/av5d7D+rpAU1SxfUhBOPNSRoJUI5NSBbokp +a7u4azdB2IQETX3d2rhDk09EbG1XmMi5Vg1oa8nxfMOWXZnDMusJoZClKjrthmwd +rtR5et44XYhX6p217RBkYMDOVFT7aZpu4SaFeqZIuarVYodSmgXToOFXPsrLppRQ +adOT0FpU64RPNrQz5NF1bSIjqrHSaRVacf8yr7qqxNnpMsrtkDJzsMBz +-----END CERTIFICATE----- diff --git a/src/leap/mail/smtp/tests/cert/server.key b/src/leap/mail/smtp/tests/cert/server.key new file mode 100644 index 0000000..197a449 --- /dev/null +++ b/src/leap/mail/smtp/tests/cert/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- -- cgit v1.2.3 From 09e6911dc8babf78b228baa0b30111390b622857 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 30 Oct 2013 11:58:22 -0200 Subject: add freeze_debianver_command --- setup.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 57a4164..499a9ee 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ """ Setup file for leap.mail """ +import re from setuptools import setup from setuptools import find_packages @@ -46,20 +47,84 @@ trove_classifiers = [ 'Topic :: Software Development :: Libraries', ] +DOWNLOAD_BASE = ('https://github.com/leapcode/leap_mail/' + 'archive/%s.tar.gz') +_versions = versioneer.get_versions() +VERSION = _versions['version'] +VERSION_FULL = _versions['full'] +DOWNLOAD_URL = "" + +# get the short version for the download url +_version_short = re.findall('\d+\.\d+\.\d+', VERSION) +if len(_version_short) > 0: + VERSION_SHORT = _version_short[0] + DOWNLOAD_URL = DOWNLOAD_BASE % VERSION_SHORT + +cmdclass = versioneer.get_cmdclass() + + +from setuptools import Command + + +class freeze_debianver(Command): + """ + Freezes the version in a debian branch. + To be used after merging the development branch onto the debian one. + """ + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + proceed = str(raw_input( + "This will overwrite the file _version.py. Continue? [y/N] ")) + if proceed != "y": + print("He. You scared. Aborting.") + return + template = r""" +# This file was generated by the `freeze_debianver` command in setup.py +# Using 'versioneer.py' (0.7+) from +# revision-control system data, or from the parent directory name of an +# unpacked source archive. Distribution tarballs contain a pre-generated copy +# of this file. + +version_version = '{version}' +version_full = '{version_full}' +""" + templatefun = r""" + +def get_versions(default={}, verbose=False): + return {'version': version_version, 'full': version_full} +""" + subst_template = template.format( + version=VERSION_SHORT, + version_full=VERSION_FULL) + templatefun + with open(versioneer.versionfile_source, 'w') as f: + f.write(subst_template) + + +cmdclass["freeze_debianver"] = freeze_debianver + # XXX add ref to docs setup( name='leap.mail', - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), + version=VERSION, + cmdclass=cmdclass, url='https://leap.se/', + download_url=DOWNLOAD_URL, license='GPLv3+', author='The LEAP Encryption Access Project', author_email='info@leap.se', + maintainer='Kali Kaneko', + maintainer_email='kali@leap.se', description='Mail Services provided by Bitmask, the LEAP Client.', - long_description=( - "Mail Services provided by Bitmask, the LEAP Client." - ), + long_description=open('README.rst').read() + '\n\n\n' + + open('CHANGELOG').read(), classifiers=trove_classifiers, namespace_packages=["leap"], package_dir={'': 'src'}, -- cgit v1.2.3 From 971058ec42c117d4173f1800ad4457047fc08a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 30 Oct 2013 15:02:16 -0300 Subject: Return port and factory from the imap launch method --- changes/bug_return_factory_and_port_imap | 2 ++ src/leap/mail/imap/service/imap.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changes/bug_return_factory_and_port_imap diff --git a/changes/bug_return_factory_and_port_imap b/changes/bug_return_factory_and_port_imap new file mode 100644 index 0000000..75f96d7 --- /dev/null +++ b/changes/bug_return_factory_and_port_imap @@ -0,0 +1,2 @@ + o Return the necessary references (factory, port) from IMAP4 launch + in order to be able to properly stop it. Related to #4199. \ No newline at end of file diff --git a/src/leap/mail/imap/service/imap.py b/src/leap/mail/imap/service/imap.py index b840e86..b641d2e 100644 --- a/src/leap/mail/imap/service/imap.py +++ b/src/leap/mail/imap/service/imap.py @@ -141,7 +141,9 @@ def run_service(*args, **kwargs): Main entry point to run the service from the client. :returns: the LoopingCall instance that will have to be stoppped - before shutting down the client. + before shutting down the client, the port as returned by + the reactor when starts listening, and the factory for + the protocol. """ leap_assert(len(args) == 2) soledad, keymanager = args @@ -157,8 +159,8 @@ def run_service(*args, **kwargs): from twisted.internet import reactor try: - reactor.listenTCP(port, factory, - interface="localhost") + tport = reactor.listenTCP(port, factory, + interface="localhost") fetcher = LeapIncomingMail( keymanager, soledad, @@ -174,7 +176,7 @@ def run_service(*args, **kwargs): fetcher.start_loop() logger.debug("IMAP4 Server is RUNNING in port %s" % (port,)) leap_events.signal(IMAP_SERVICE_STARTED, str(port)) - return fetcher + return fetcher, tport, factory # not ok, signal error. leap_events.signal(IMAP_SERVICE_FAILED_TO_START, str(port)) -- cgit v1.2.3 From 4efecad56eae8d777d2fa0d39c4de13b022c7bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 31 Oct 2013 10:18:47 -0300 Subject: Refactor out get_email_charset to leap.common --- changes/bug_refactor_utils | 1 + src/leap/mail/imap/fetch.py | 2 +- src/leap/mail/imap/server.py | 2 +- src/leap/mail/utils.py | 44 -------------------------------------------- 4 files changed, 3 insertions(+), 46 deletions(-) create mode 100644 changes/bug_refactor_utils delete mode 100644 src/leap/mail/utils.py diff --git a/changes/bug_refactor_utils b/changes/bug_refactor_utils new file mode 100644 index 0000000..8ba697a --- /dev/null +++ b/changes/bug_refactor_utils @@ -0,0 +1 @@ + o Refactor get_email_charset to leap.common. \ No newline at end of file diff --git a/src/leap/mail/imap/fetch.py b/src/leap/mail/imap/fetch.py index a776ac7..2775d71 100644 --- a/src/leap/mail/imap/fetch.py +++ b/src/leap/mail/imap/fetch.py @@ -40,7 +40,7 @@ from leap.common.events.events_pb2 import IMAP_MSG_DECRYPTED from leap.common.events.events_pb2 import IMAP_MSG_SAVED_LOCALLY from leap.common.events.events_pb2 import IMAP_MSG_DELETED_INCOMING from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL -from leap.mail.utils import get_email_charset +from leap.common.mail import get_email_charset logger = logging.getLogger(__name__) diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 5a98315..9e3e23e 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -34,8 +34,8 @@ from twisted.python import log from leap.common import events as leap_events from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL from leap.common.check import leap_assert, leap_assert_type +from leap.common.mail import get_email_charset from leap.soledad.client import Soledad -from leap.mail.utils import get_email_charset logger = logging.getLogger(__name__) diff --git a/src/leap/mail/utils.py b/src/leap/mail/utils.py deleted file mode 100644 index 22e16a7..0000000 --- a/src/leap/mail/utils.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# utils.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 . -""" -Utility functions for email. -""" -import email -import re - - -def get_email_charset(content): - """ - Mini parser to retrieve the charset of an email. - - :param content: mail contents - :type content: unicode - - :returns: the charset as parsed from the contents - :rtype: str - """ - charset = "UTF-8" - try: - em = email.message_from_string(content.encode("utf-8")) - # Miniparser for: Content-Type: ; charset= - charset_re = r'''charset=(?P[\w|\d|-]*)''' - charset = re.findall(charset_re, em["Content-Type"])[0] - if charset is None or len(charset) == 0: - charset = "UTF-8" - except Exception: - pass - return charset -- cgit v1.2.3 From dc2ba7f1741ddc6375adcc51469279842efbf54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 31 Oct 2013 13:18:08 -0300 Subject: Add version compat line --- changes/VERSION_COMPAT | 1 + 1 file changed, 1 insertion(+) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index cc00ecf..07fa24e 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,3 +8,4 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z +leap.common>=0.3.5 \ No newline at end of file -- cgit v1.2.3 From 64cbb0d02e312a91c324b49ddb99bfb691b4e2cd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 31 Oct 2013 22:34:50 -0200 Subject: notify MUA of new mail as it gets added to mailbox --- changes/feature-mail-notifications | 1 + src/leap/mail/imap/fetch.py | 44 ++++++++++++++++++++++++++++++---- src/leap/mail/imap/server.py | 48 ++++++++++++++++++++++++++++++-------- src/leap/mail/imap/service/imap.py | 4 ++-- 4 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 changes/feature-mail-notifications diff --git a/changes/feature-mail-notifications b/changes/feature-mail-notifications new file mode 100644 index 0000000..8e52493 --- /dev/null +++ b/changes/feature-mail-notifications @@ -0,0 +1 @@ + o Notify MUA of new mail, using IDLE as advertised. Closes: #3671 diff --git a/src/leap/mail/imap/fetch.py b/src/leap/mail/imap/fetch.py index 2775d71..dd65def 100644 --- a/src/leap/mail/imap/fetch.py +++ b/src/leap/mail/imap/fetch.py @@ -31,9 +31,6 @@ from twisted.internet.threads import deferToThread from leap.common import events as leap_events from leap.common.check import leap_assert, leap_assert_type -from leap.soledad.client import Soledad -from leap.soledad.common.crypto import ENC_SCHEME_KEY, ENC_JSON_KEY - from leap.common.events.events_pb2 import IMAP_FETCHED_INCOMING from leap.common.events.events_pb2 import IMAP_MSG_PROCESSING from leap.common.events.events_pb2 import IMAP_MSG_DECRYPTED @@ -41,6 +38,9 @@ from leap.common.events.events_pb2 import IMAP_MSG_SAVED_LOCALLY from leap.common.events.events_pb2 import IMAP_MSG_DELETED_INCOMING from leap.common.events.events_pb2 import IMAP_UNREAD_MAIL from leap.common.mail import get_email_charset +from leap.keymanager import errors as keymanager_errors +from leap.soledad.client import Soledad +from leap.soledad.common.crypto import ENC_SCHEME_KEY, ENC_JSON_KEY logger = logging.getLogger(__name__) @@ -198,6 +198,29 @@ class LeapIncomingMail(object): logger.warning('Unknown error while ' 'syncing soledad: %r' % (err,)) + def _log_err(self, failure): + """ + Generic errback + """ + err = failure.value + logger.error("error!: %r" % (err,)) + + def _decryption_error(self, failure): + """ + Errback for decryption errors. + """ + # XXX should signal unrecoverable maybe. + err = failure.value + logger.error("error decrypting msg: %s" % (err,)) + + def _saving_error(self, failure): + """ + Errback for local save errors. + """ + # XXX should signal unrecoverable maybe. + err = failure.value + logger.error("error saving msg locally: %s" % (err,)) + def _process_doclist(self, doclist): """ Iterates through the doclist, checks if each doc @@ -228,7 +251,13 @@ class LeapIncomingMail(object): # Deferred chain for individual messages d = deferToThread(self._decrypt_msg, doc, encdata) d.addCallback(self._process_decrypted) + d.addErrback(self._log_err) d.addCallback(self._add_message_locally) + d.addErrback(self._log_err) + # XXX check this, add_locally should not get called if we + # get an error in process + #d.addCallbacks(self._process_decrypted, self._decryption_error) + #d.addCallbacks(self._add_message_locally, self._saving_error) docs_cb.append(d) else: # Ooops, this does not. @@ -289,8 +318,12 @@ class LeapIncomingMail(object): rawmsg = msg.get(self.CONTENT_KEY, None) if not rawmsg: return False - data = self._maybe_decrypt_gpg_msg(rawmsg) - return doc, data + try: + data = self._maybe_decrypt_gpg_msg(rawmsg) + return doc, data + except keymanager_errors.EncryptionDecryptionFailed as exc: + logger.error(exc) + raise def _maybe_decrypt_gpg_msg(self, data): """ @@ -384,6 +417,7 @@ class LeapIncomingMail(object): incoming message :type msgtuple: (SoledadDocument, str) """ + print "adding message locally....." doc, data = msgtuple self._inbox.addMessage(data, (self.RECENT_FLAG,)) leap_events.signal(IMAP_MSG_SAVED_LOCALLY) diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 9e3e23e..7a9f810 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -573,7 +573,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): return "" % self._account_name ####################################### -# Soledad Message, MessageCollection +# LeapMessage, MessageCollection # and Mailbox ####################################### @@ -1099,6 +1099,7 @@ class SoledadMailbox(WithMsgFields): which we instantiate and make accessible in the `messages` attribute. """ implements(imap4.IMailboxInfo, imap4.IMailbox, imap4.ICloseableMailbox) + # XXX should finish the implementation of IMailboxListener messages = None _closed = False @@ -1115,6 +1116,8 @@ class SoledadMailbox(WithMsgFields): CMD_UIDVALIDITY = "UIDVALIDITY" CMD_UNSEEN = "UNSEEN" + listeners = [] + def __init__(self, mbox, soledad=None, rw=1): """ SoledadMailbox constructor. Needs to get passed a name, plus a @@ -1147,15 +1150,35 @@ class SoledadMailbox(WithMsgFields): if not self.getFlags(): self.setFlags(self.INIT_FLAGS) - # XXX what is/was this used for? -------- - # ---> mail/imap4.py +1155, - # _cbSelectWork makes use of this - # probably should implement hooks here - # using leap.common.events - self.listeners = [] - self.addListener = self.listeners.append - self.removeListener = self.listeners.remove - #------------------------------------------ + # the server itself is a listener to the mailbox. + # so we can notify it (and should!) after chanes in flags + # and number of messages. + print "emptying the listeners" + map(lambda i: self.listeners.remove(i), self.listeners) + + def addListener(self, listener): + """ + Rdds a listener to the listeners queue. + + :param listener: listener to add + :type listener: an object that implements IMailboxListener + """ + logger.debug('adding mailbox listener: %s' % listener) + self.listeners.append(listener) + + def removeListener(self, listener): + """ + Removes a listener from the listeners queue. + + :param listener: listener to remove + :type listener: an object that implements IMailboxListener + """ + logger.debug('removing mailbox listener: %s' % listener) + try: + self.listeners.remove(listener) + except ValueError: + logger.error( + "removeListener: cannot remove listener %s" % listener) def _get_mbox(self): """ @@ -1180,6 +1203,7 @@ class SoledadMailbox(WithMsgFields): #return map(str, self.INIT_FLAGS) # XXX CHECK against thunderbird XXX + # XXX I think this is slightly broken.. :/ mbox = self._get_mbox() if not mbox: @@ -1352,6 +1376,10 @@ class SoledadMailbox(WithMsgFields): self.messages.add_msg(message, flags=flags, date=date, uid=uid_next) + exists = len(self.messages) + recent = len(self.messages.get_recent()) + for listener in self.listeners: + listener.newMessages(exists, recent) return defer.succeed(None) # commands, do not rename methods diff --git a/src/leap/mail/imap/service/imap.py b/src/leap/mail/imap/service/imap.py index b641d2e..5f7322a 100644 --- a/src/leap/mail/imap/service/imap.py +++ b/src/leap/mail/imap/service/imap.py @@ -77,7 +77,7 @@ class LeapIMAPServer(imap4.IMAP4Server): #self.theAccount = theAccount def lineReceived(self, line): - if "login" in line: + if "login" in line.lower(): # avoid to log the pass, even though we are using a dummy auth # by now. msg = line[:7] + " [...]" @@ -160,7 +160,7 @@ def run_service(*args, **kwargs): try: tport = reactor.listenTCP(port, factory, - interface="localhost") + interface="localhost") fetcher = LeapIncomingMail( keymanager, soledad, -- cgit v1.2.3 From 1bb3748a9617ab641710113e4f29d9bab0b4487c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 1 Nov 2013 10:41:45 -0300 Subject: Update requirements --- changes/VERSION_COMPAT | 1 - pkg/requirements.pip | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index 07fa24e..cc00ecf 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,4 +8,3 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z -leap.common>=0.3.5 \ No newline at end of file diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 6fa0df4..4780b5c 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -1,4 +1,4 @@ leap.soledad.client>=0.3.0 -leap.common>=0.3.0 +leap.common>=0.3.5 leap.keymanager>=0.3.4 twisted # >= 12.0.3 ?? -- cgit v1.2.3 From f2076cf22b889a332d497e8db0e0602bbc592336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 1 Nov 2013 10:42:58 -0300 Subject: Fold in changes --- CHANGELOG | 10 ++++++++++ changes/bug-4000_support-non-ascii | 1 - changes/bug_default_to_utf8 | 2 -- changes/bug_refactor_utils | 1 - changes/bug_return_factory_and_port_imap | 2 -- changes/feature-mail-notifications | 1 - changes/feature_3637-use-tls-wrapper-mode | 1 - 7 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 changes/bug-4000_support-non-ascii delete mode 100644 changes/bug_default_to_utf8 delete mode 100644 changes/bug_refactor_utils delete mode 100644 changes/bug_return_factory_and_port_imap delete mode 100644 changes/feature-mail-notifications delete mode 100644 changes/feature_3637-use-tls-wrapper-mode diff --git a/CHANGELOG b/CHANGELOG index 319fda5..5755e59 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +0.3.6 Nov 1: + o Add support for non-ascii characters in emails. Closes #4000. + o Default to UTF-8 when there is no charset parsed from the mail + contents. + o Refactor get_email_charset to leap.common. + o Return the necessary references (factory, port) from IMAP4 launch + in order to be able to properly stop it. Related to #4199. + o Notify MUA of new mail, using IDLE as advertised. Closes: #3671 + o Use TLS wrapper mode instead of STARTTLS. Closes #3637. + 0.3.5 Oct 18: o Do not log mail doc contents. o Comply with RFC 3156. Closes #4029. diff --git a/changes/bug-4000_support-non-ascii b/changes/bug-4000_support-non-ascii deleted file mode 100644 index 8f6712d..0000000 --- a/changes/bug-4000_support-non-ascii +++ /dev/null @@ -1 +0,0 @@ - o Add support for non-ascii characters in emails. Closes #4000. diff --git a/changes/bug_default_to_utf8 b/changes/bug_default_to_utf8 deleted file mode 100644 index 898138b..0000000 --- a/changes/bug_default_to_utf8 +++ /dev/null @@ -1,2 +0,0 @@ - o Default to UTF-8 when there is no charset parsed from the mail - contents. \ No newline at end of file diff --git a/changes/bug_refactor_utils b/changes/bug_refactor_utils deleted file mode 100644 index 8ba697a..0000000 --- a/changes/bug_refactor_utils +++ /dev/null @@ -1 +0,0 @@ - o Refactor get_email_charset to leap.common. \ No newline at end of file diff --git a/changes/bug_return_factory_and_port_imap b/changes/bug_return_factory_and_port_imap deleted file mode 100644 index 75f96d7..0000000 --- a/changes/bug_return_factory_and_port_imap +++ /dev/null @@ -1,2 +0,0 @@ - o Return the necessary references (factory, port) from IMAP4 launch - in order to be able to properly stop it. Related to #4199. \ No newline at end of file diff --git a/changes/feature-mail-notifications b/changes/feature-mail-notifications deleted file mode 100644 index 8e52493..0000000 --- a/changes/feature-mail-notifications +++ /dev/null @@ -1 +0,0 @@ - o Notify MUA of new mail, using IDLE as advertised. Closes: #3671 diff --git a/changes/feature_3637-use-tls-wrapper-mode b/changes/feature_3637-use-tls-wrapper-mode deleted file mode 100644 index 668015f..0000000 --- a/changes/feature_3637-use-tls-wrapper-mode +++ /dev/null @@ -1 +0,0 @@ - o Use TLS wrapper mode instead of STARTTLS. Closes #3637. -- cgit v1.2.3