예제 #1
0
파일: compose.py 프로젝트: zsdlove/Mailpile
    def _create_from_to_cc(cls, idx, session, trees):
        config = session.config
        ahp = AddressHeaderParser()
        ref_from, ref_to, ref_cc = [], [], []
        result = {'from': '', 'to': [], 'cc': []}

        def merge_contact(ai):
            vcard = config.vcards.get_vcard(ai.address)
            if vcard:
                ai.merge_vcard(vcard)
            return ai

        # Parse the headers, so we know what we're working with. We prune
        # some of the duplicates at this stage.
        for addrs in [t['addresses'] for t in trees]:
            alist = []
            for dst, addresses in ((ref_from, addrs.get('reply-to')
                                    or addrs.get('from', [])),
                                   (ref_to, addrs.get('to', [])),
                                   (ref_cc, addrs.get('cc', []))):
                alist += [d.address for d in dst]
                dst.extend([a for a in addresses if a.address not in alist])

        # 1st, choose a from address.
        from_ai = config.vcards.choose_from_address(
            config, ref_from, ref_to, ref_cc)  # Note: order matters!
        if from_ai:
            result['from'] = ahp.normalized(addresses=[from_ai],
                                            force_name=True)

        def addresses(addrs, exclude=[]):
            alist = [from_ai.address] if (from_ai) else []
            alist += [a.address for a in exclude]
            return [
                merge_contact(a) for a in addrs
                if a.address not in alist and not a.address.startswith(
                    'noreply@') and '@noreply' not in a.address
            ]

        # If only replying to messages sent from chosen from, then this is
        # a follow-up or clarification, so just use the same headers.
        if (from_ai and len([
                e for e in ref_from if e and e.address == from_ai.address
        ]) == len(ref_from)):
            if ref_to:
                result['to'] = addresses(ref_to)
            if ref_cc:
                result['cc'] = addresses(ref_cc)

        # Else, if replying to other people:
        #   - Construct To from the From lines, excluding own from
        #   - Construct Cc from the To and CC lines, except new To/From
        else:
            result['to'] = addresses(ref_from)
            result['cc'] = addresses(ref_to + ref_cc, exclude=ref_from)

        return result
예제 #2
0
    def _create_from_to_cc(cls, idx, session, trees):
        config = session.config
        ahp = AddressHeaderParser()
        ref_from, ref_to, ref_cc = [], [], []
        result = {'from': '', 'to': [], 'cc': []}

        def merge_contact(ai):
            vcard = config.vcards.get_vcard(ai.address)
            if vcard:
                ai.merge_vcard(vcard)
            return ai

        # Parse the headers, so we know what we're working with. We prune
        # some of the duplicates at this stage.
        for addrs in [t['addresses'] for t in trees]:
            alist = []
            for dst, addresses in (
                    (ref_from, addrs.get('reply-to') or addrs.get('from', [])),
                    (ref_to, addrs.get('to', [])),
                    (ref_cc, addrs.get('cc', []))):
                alist += [d.address for d in dst]
                dst.extend([a for a in addresses if a.address not in alist])

        # 1st, choose a from address.
        from_ai = config.vcards.choose_from_address(
            config, ref_from, ref_to, ref_cc)  # Note: order matters!
        if from_ai:
            result['from'] = ahp.normalized(addresses=[from_ai],
                                            force_name=True)

        def addresses(addrs, exclude=[]):
            alist = [from_ai.address] if (from_ai) else []
            alist += [a.address for a in exclude]
            return [merge_contact(a) for a in addrs
                    if a.address not in alist
                    and not a.address.startswith('noreply@')
                    and '@noreply' not in a.address]

        # If only replying to messages sent from chosen from, then this is
        # a follow-up or clarification, so just use the same headers.
        if (from_ai and
               len([e for e in ref_from
                    if e and e.address == from_ai.address]) == len(ref_from)):
            if ref_to:
                result['to'] = addresses(ref_to)
            if ref_cc:
                result['cc'] = addresses(ref_cc)

        # Else, if replying to other people:
        #   - Construct To from the From lines, excluding own from
        #   - Construct Cc from the To and CC lines, except new To/From
        else:
            result['to'] = addresses(ref_from)
            result['cc'] = addresses(ref_to + ref_cc, exclude=ref_from)

        return result
예제 #3
0
def extract_autocrypt_header(msg, to=None, optional_attrs=None):
    # Autocrypt requires there only be one From header
    froms = msg.get_all("From") or []
    if len(froms) != 1:
        return {}

    # Extract the from address for comparisons below. We compare the
    # canonicalized versions, which is not the strictest interpretation
    # of the spec, but feels like a reasonable balance here.
    from mailpile.mailutils.addresses import AddressHeaderParser
    from_addrs = AddressHeaderParser(froms[0])
    if len(from_addrs) != 1:
        return {}
    from_addr = canonicalize_email(from_addrs[0].address)

    to = canonicalize_email(to) if to else None
    all_results = []
    for inb in (msg.get_all("Autocrypt") or []):
        res = parse_autocrypt_headervalue(inb, optional_attrs=optional_attrs)
        if res:
            if ((not to or canonicalize_email(res['addr']) == to)
                    and (canonicalize_email(res['addr']) == from_addr)):
                all_results.append(res)

    # Return parsed header iff we found exactly one.
    if len(all_results) == 1:
        return all_results[0]
    else:
        return {}
예제 #4
0
def ObscureNames(hdr):
    """
    Remove names (leaving e-mail addresses) from the To: and Cc: headers.

    >>> ObscureNames("Bjarni R. E. <*****@*****.**>, [email protected] (Elmer Boop)")
    u'<*****@*****.**>, <[email protected]>'

    """
    from mailpile.mailutils.addresses import AddressHeaderParser
    return ', '.join('<%s>' % ai.address for ai in AddressHeaderParser(hdr))
예제 #5
0
 def _message_to_msg_info(self, msg_idx_pos, msg_ptr, msg):
     msg_mid = b36(msg_idx_pos)
     msg_to = AddressHeaderParser(msg.get('to'))
     msg_cc = AddressHeaderParser(msg.get('cc'))
     msg_cc += AddressHeaderParser(msg.get('bcc'))
     return [
         msg_mid,
         msg_ptr,                          # Message PTR
         self.get_msg_id(msg, msg_ptr),    # Message ID
         b36(safe_message_ts(msg)),        # Message timestamp
         safe_decode_hdr(msg, 'from'),     # Message from
         self.compact_to_list(msg_to),     # Compacted to-list
         self.compact_to_list(msg_cc),     # Compacted cc/bcc-list
         b36(len(msg) // 1024),            # Message size
         safe_decode_hdr(msg, 'subject'),  # Subject
         self.MSG_BODY_LAZY,               # Body snippets come later
         '',                               # Tags
         '',                               # Replies
         msg_mid]                          # Thread
예제 #6
0
    def _get_addresses(self, pairs, name):
        from mailpile.mailutils.addresses import AddressHeaderParser
        config = self.env.session.config

        addresses = []
        for hdr in self._get_all(pairs, name):
            addresses.extend(AddressHeaderParser(unicode_data=hdr))

        for ai in addresses:
            vcard = config.vcards.get_vcard(ai.address)
            if vcard:
                ai.merge_vcard(vcard)

        return addresses
예제 #7
0
    def _message(self, email):
        problem, tree = None, {}
        try:
            # We load the message in stages (relying on the internal cache
            # to make this not slow), so we can report more accurately what
            # has failed.
            problem = _('Failed to load and parse message data.')
            msg = email.get_msg(pgpmime=False)

            problem = _('Failed process message crypto (decrypt, etc).')
            msg = email.get_msg(pgpmime='default')

            problem = _('Failed to parse message.')
            tree = email.get_message_tree(want=(email.WANT_MSG_TREE_PGP +
                                                self.WANT_MSG_TREE))

            problem = _('Failed process message crypto (decrypt, etc).')
            email.evaluate_pgp(tree, decrypt=True)

            problem = _("Failed to evalute sender trust")
            evaluate_sender_trust(self.session.config, email, tree)

            editing_strings = tree.get('editing_strings')
            if editing_strings:
                for key in ('from', 'to', 'cc', 'bcc'):
                    problem = _("Failed to parse %s headers." % key)
                    if key in editing_strings:
                        cids = self._msg_addresses(
                            addresses=AddressHeaderParser(
                                unicode_data=editing_strings[key]))
                        editing_strings['%s_aids' % key] = cids
                        for cid in cids:
                            if cid not in self['data']['addresses']:
                                self['data']['addresses'][cid] = self._address(
                                    cid=cid)
            problem = None

        except Exception as e:
            if problem:
                problem += ' ' + _('Message may be corrupt!')
            details = {
                'error': unicode(e),
                'details': problem,
                'traceback': traceback.format_exc(e)
            }
            details.update(self._troubleshoot_missing_message(email, tree))
            self['errors'] = self.get('errors', {})
            self['errors'][email.msg_mid()] = details

        return self._prune_msg_tree(tree)
예제 #8
0
    def TransformOutgoing(self, sender, rcpts, msg, **kwargs):
        matched = False
        gnupg = None
        sender_keyid = None

        # Prefer to just get everything from the profile VCard, in the
        # common case...
        profile = self._get_sender_profile(sender, kwargs)
        if profile['vcard'] is not None:
            sender_keyid = profile['vcard'].pgp_key
        crypto_format = profile.get('crypto_format') or 'none'

        # Parse the openpgp_header data from the crypto_format
        openpgp_header = [p.split(':')[-1]
                          for p in crypto_format.split('+')
                          if p.startswith('openpgp_header:')]
        if not openpgp_header:
            openpgp_header = self.config.prefs.openpgp_header and ['CFG']

        if openpgp_header[0] != 'N' and not sender_keyid:
            # This is a fallback: this shouldn't happen much in normal use
            try:
                gnupg = gnupg or GnuPG(self.config, event=GetThreadEvent())
                seckeys = dict([(uid["email"], fp) for fp, key
                                in gnupg.list_secret_keys().iteritems()
                                if key["capabilities_map"].get("encrypt")
                                and key["capabilities_map"].get("sign")
                                for uid in key["uids"]])
                sender_keyid = seckeys.get(sender)
            except (KeyError, TypeError, IndexError, ValueError):
                traceback.print_exc()

        if sender_keyid and openpgp_header:
            preference = {
                'ES': 'signencrypt',
                'SE': 'signencrypt',
                'E': 'encrypt',
                'S': 'sign',
                'N': 'unprotected',
                'CFG': self.config.prefs.openpgp_header
            }[openpgp_header[0].upper()]
            msg["OpenPGP"] = ("id=%s; preference=%s"
                              % (sender_keyid, preference))

        if ('attach-pgp-pubkey' in msg and
                msg['attach-pgp-pubkey'][:3].lower() in ('yes', 'tru')):
            gnupg = gnupg or GnuPG(self.config, event=GetThreadEvent())
            if sender_keyid:
                keys = gnupg.list_keys(selectors=[sender_keyid])
            else:
                keys = gnupg.address_to_keys(AddressHeaderParser(sender).addresses_list()[0])

            key_count = 0
            for fp, key in keys.iteritems():
                if not any(key["capabilities_map"].values()):
                    continue
                # We should never really hit this more than once. But if we
                # do, should still be fine.
                keyid = key["keyid"]
                data = gnupg.get_pubkey(keyid)

                try:
                    from_name = key["uids"][0]["name"]
                    filename = _('Encryption key for %s') % from_name
                except:
                    filename = _('My encryption key')

                if self.config.prefs.gpg_html_wrap:
                    data = self._wrap_key_in_html(filename, data)
                    ext = 'html'
                else:
                    ext = 'asc'

                att = MIMEBase('application', 'pgp-keys')
                att.set_payload(data)
                encoders.encode_base64(att)
                del att['MIME-Version']
                att.add_header('Content-Id', MakeContentID())
                att.add_header('Content-Disposition', 'attachment',
                               filename=filename + '.' + ext)
                att.signature_info = SignatureInfo(parent=msg.signature_info)
                att.encryption_info = EncryptionInfo(parent=msg.encryption_info)
                msg.attach(att)
                key_count += 1

            if key_count > 0:
                msg['x-mp-internal-pubkeys-attached'] = "Yes"

        return sender, rcpts, msg, matched, True
예제 #9
0
    def command(self, emails=None):
        session, config, idx = self.session, self.session.config, self._idx()
        args = list(self.args)

        bounce_to = []
        while args and '@' in args[-1]:
            bounce_to.append(args.pop(-1))
        for rcpt in (self.data.get('to', []) +
                     self.data.get('cc', []) +
                     self.data.get('bcc', [])):
            bounce_to.extend(AddressHeaderParser(rcpt).addresses_list())

        sender = self.data.get('from', [None])[0]
        if not sender and bounce_to:
            sender = idx.config.get_profile().get('email', None)

        if not emails:
            args.extend(['=%s' % mid for mid in self.data.get('mid', [])])
            emails = [self._actualize_ephemeral(i) for i in
                      self._choose_messages(args, allow_ephemeral=True)]

        # First make sure the draft tags are all gone, so other edits either
        # fail or complete while we wait for the lock.
        with GLOBAL_EDITING_LOCK:
            self._tag_drafts(emails, untag=True)
            self._tag_blank(emails, untag=True)

        # Process one at a time so we don't eat too much memory
        sent = []
        missing_keys = []
        locked_keys = []
        for email in emails:
            events = []
            try:
                msg_mid = email.get_msg_info(idx.MSG_MID)

                # This is a unique sending-ID. This goes in the public (meant
                # for debugging help) section of the event-log, so we take
                # care to not reveal details about the message or recipients.
                msg_sid = sha1b64(email.get_msg_info(idx.MSG_ID),
                                  *sorted(bounce_to))[:8]

                # We load up any incomplete events for sending this message
                # to this set of recipients. If nothing is in flight, create
                # a new event for tracking this operation.
                events = list(config.event_log.incomplete(
                    source=self.EVENT_SOURCE,
                    data_mid=msg_mid,
                    data_sid=msg_sid))
                if not events:
                    events.append(config.event_log.log(
                        source=self.EVENT_SOURCE,
                        flags=Event.RUNNING,
                        message=_('Sending message'),
                        data={'mid': msg_mid, 'sid': msg_sid}))

                SendMail(session, msg_mid,
                         [PrepareMessage(config,
                                         email.get_msg(pgpmime=False),
                                         sender=sender,
                                         rcpts=(bounce_to or None),
                                         bounce=(True if bounce_to else False),
                                         events=events)])
                for ev in events:
                    ev.flags = Event.COMPLETE
                    config.event_log.log_event(ev)
                sent.append(email)

            # Encryption related failures are fatal, don't retry
            except (KeyLookupError,
                    EncryptionFailureError,
                    SignatureFailureError), exc:
                message = unicode(exc)
                session.ui.warning(message)
                if hasattr(exc, 'missing_keys'):
                    missing_keys.extend(exc.missing)
                if hasattr(exc, 'from_key'):
                    # FIXME: We assume signature failures happen because
                    # the key is locked. Are there any other reasons?
                    locked_keys.append(exc.from_key)
                for ev in events:
                    ev.flags = Event.COMPLETE
                    ev.message = message
                    config.event_log.log_event(ev)
                self._ignore_exception()

            # FIXME: Also fatal, when the SMTP server REJECTS the mail
            except: