diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2012-05-17 15:47:23 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2012-05-17 15:47:23 +0100 |
commit | 08c2ee1d88e69aa5df27b74d1cebd3c0c8be68f6 (patch) | |
tree | 2dde7d7db9146918242f2c289c64e587706b3f09 /alot/db/envelope.py | |
parent | 138f13c682f6a63da67cced1f0c267b5f6f349b8 (diff) | |
parent | 56b0b8a0f310403d4664649ab9b69d3cd765dbe5 (diff) |
Merge branch '0.3-feature-pyme' into staging
Conflicts:
alot/settings/__init__.py
alot/settings/checks.py
docs/source/generate_configs.py
Diffstat (limited to 'alot/db/envelope.py')
-rw-r--r-- | alot/db/envelope.py | 81 |
1 files changed, 68 insertions, 13 deletions
diff --git a/alot/db/envelope.py b/alot/db/envelope.py index f84d305a..14859d9f 100644 --- a/alot/db/envelope.py +++ b/alot/db/envelope.py @@ -1,15 +1,23 @@ +# vim:ts=4:sw=4:expandtab import os import email import re import email.charset as charset charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8') +from email.encoders import encode_7or8bit from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication +import pyme.core +import pyme.constants +import pyme.errors from alot import __version__ import logging import alot.helper as helper +import alot.crypto as crypto from alot.settings import settings +from alot.errors import GPGProblem from attachment import Attachment from utils import encode_header @@ -18,7 +26,7 @@ from utils import encode_header class Envelope(object): """a message that is not yet sent and still editable""" def __init__(self, template=None, bodytext=u'', headers={}, attachments=[], - sign=False, encrypt=False): + sign=False, sign_key=None, encrypt=False): """ :param template: if not None, the envelope will be initialised by :meth:`parsing <parse_template>` this string before @@ -44,6 +52,7 @@ class Envelope(object): self.headers.update(headers) self.attachments = list(attachments) self.sign = sign + self.sign_key = sign_key self.encrypt = encrypt self.sent_time = None self.modified_since_sent = False @@ -133,15 +142,65 @@ class Envelope(object): compiles the information contained in this envelope into a :class:`email.Message`. """ - # build body text part - textpart = MIMEText(self.body.encode('utf-8'), 'plain', 'utf-8') + # Build body text part. To properly sign/encrypt messages later on, we + # convert the text to its canonical format (as per RFC 2015). + canonical_format = self.body.encode('utf-8') + canonical_format = canonical_format.replace('\\t', ' '*4) + textpart = MIMEText(canonical_format, 'plain', 'utf-8') # wrap it in a multipart container if necessary - if self.attachments or self.sign or self.encrypt: - msg = MIMEMultipart() - msg.attach(textpart) + if self.attachments: + inner_msg = MIMEMultipart() + inner_msg.attach(textpart) + # add attachments + for a in self.attachments: + inner_msg.attach(a.get_mime_representation()) else: - msg = textpart + inner_msg = textpart + + if self.sign: + context = crypto.CryptoContext() + + plaintext = crypto.email_as_string(inner_msg) + logging.info('signing plaintext: ' + plaintext) + + try: + result, signature_str = context.detached_signature_for( + plaintext, self.sign_key) + if len(result.signatures) != 1: + raise GPGProblem(("Could not sign message " + "(GPGME did not return a signature)")) + except pyme.errors.GPGMEError as e: + # 11 == GPG_ERR_BAD_PASSPHRASE + if e.getcode() == 11: + # If GPG_AGENT_INFO is unset or empty, the user just does + # not have gpg-agent running (properly). + if os.environ.get('GPG_AGENT_INFO', '').strip() == '': + raise GPGProblem(("Bad passphrase and " + "GPG_AGENT_INFO not set. Please setup " + "gpg-agent.")) + else: + raise GPGProblem(("Bad passphrase. Is " + "gpg-agent running?")) + raise GPGProblem(str(e)) + + micalg = crypto.RFC3156_micalg_from_result(result) + outer_msg = MIMEMultipart('signed', micalg=micalg, + protocol='application/pgp-signature') + + # wrap signature in MIMEcontainter + signature_mime = MIMEApplication(_data=signature_str, + _subtype='pgp-signature; name="signature.asc"', + _encoder=encode_7or8bit) + signature_mime['Content-Description'] = 'signature' + signature_mime.set_charset('us-ascii') + + # add signed message and signature to outer message + outer_msg.attach(inner_msg) + outer_msg.attach(signature_mime) + outer_msg['Content-Disposition'] = 'inline' + else: + outer_msg = inner_msg headers = self.headers.copy() # add Message-ID @@ -159,13 +218,9 @@ class Envelope(object): # copy headers from envelope to mail for k, vlist in headers.items(): for v in vlist: - msg[k] = encode_header(k, v) - - # add attachments - for a in self.attachments: - msg.attach(a.get_mime_representation()) + outer_msg[k] = encode_header(k, v) - return msg + return outer_msg def parse_template(self, tmp, reset=False, only_body=False): """parses a template or user edited string to fills this envelope. |