Esempio n. 1
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
Esempio n. 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
Esempio n. 3
0
    def command(self):
        idx, vcards = self._idx(), self.session.config.vcards

        emails = [e for e in self.args if '@' in e]
        emails.extend(self.data.get('email', []))

        messages = self._choose_messages(
            [m for m in self.args if '@' not in m] +
            ['=%s' % mid for mid in self.data.get('mid', [])])
        for msg_idx_pos in messages:
            try:
                msg_info = idx.get_msg_at_idx_pos(msg_idx_pos)
                msg_emails = (idx.expand_to_list(msg_info, field=idx.MSG_TO) +
                              idx.expand_to_list(msg_info, field=idx.MSG_CC))
                emails.extend(msg_emails)
                if 'no_from' not in self.data:
                    emails.append(msg_info[idx.MSG_FROM])
            except ValueError:
                pass

        addrs = [
            ai for ee in emails for ai in AddressHeaderParser(unicode_data=ee)
        ]
        return self._success(_('Choosing from address'),
                             result={
                                 'emails':
                                 addrs,
                                 'from':
                                 vcards.choose_from_address(
                                     self.session.config, addrs)
                             })
Esempio n. 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 import AddressHeaderParser
    return ', '.join('<%s>' % ai.address for ai in AddressHeaderParser(hdr))
Esempio n. 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
Esempio n. 6
0
    def _get_addresses(self, pairs, name):
        from mailpile.mailutils 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
Esempio n. 7
0
    def _message(self, email):
        tree = email.get_message_tree(want=(email.WANT_MSG_TREE_PGP +
                                            self.WANT_MSG_TREE))
        email.evaluate_pgp(tree, decrypt=True)

        editing_strings = tree.get('editing_strings')
        if editing_strings:
            for key in ('from', 'to', 'cc', 'bcc'):
                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)

        return self._prune_msg_tree(tree)
Esempio n. 8
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 = session.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. We'll use the system default if
        # nothing is found, but hopefully we'll find an address we
        # recognize in one of the headers.
        from_address = (session.config.prefs.default_email or
                        session.config.profiles[0].email)
        profile_emails = [p.email for p in session.config.profiles if p.email]
        for src in (ref_from, ref_to, ref_cc):
            matches = [s for s in src if s.address in profile_emails]
            if matches:
                from_address = matches[0].address
                break
        result['from'] = ahp.normalized(addresses=[AddressInfo(p.email, p.name)
            for p in session.config.profiles if p.email == from_address],
                                        force_name=True)

        def addresses(addrs, exclude=[]):
            alist = [from_address] + [a.address for a in exclude]
            return ahp.normalized_addresses(addresses=[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],
                                            with_keys=True,
                                            force_name=True)

        # If only replying to messages sent from chosen from, then this is
        # a follow-up or clarification, so just use the same headers.
        if len([e for e in ref_from if e.address == from_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
Esempio n. 9
0
#
# In practice, we do this in two passes - first a strict pass where we try
# to parse things semi-sensibly.  If that fails, there is a second pass
# where we try to cope with certain types of weirdness we've seen in the
# wild. The wild can be pretty wild.
#
# This parser is NOT fully RFC2822 compliant - in particular it will get
# confused by nested comments (see FIXME in tests below).
#
import sys
import traceback

from mailpile.mailutils import AddressHeaderParser as AHP


ahp_tests = AHP(AHP.TEST_HEADER_DATA)
print '_tokens: {0!s}'.format(ahp_tests._tokens)
print '_groups: {0!s}'.format(ahp_tests._groups)
print '{0!s}'.format(ahp_tests)
print 'normalized: {0!s}'.format(ahp_tests.normalized())


headers, header, inheader = {}, None, False
for line in sys.stdin:
    if inheader:
        if line in ('\n', '\r\n'):
            for hdr in ('from', 'to', 'cc'):
                val = headers.get(hdr, '').replace('\n', ' ').strip()
                if val:
                    try:
                        nv = AHP(val, _raise=True).normalized()
Esempio n. 10
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.config.vcards.get_vcard(sender)
        if profile:
            sender_keyid = profile.pgp_key
            crypto_format = profile.crypto_format or 'none'
        else:
            crypto_format = '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
Esempio n. 11
0
#
# In practice, we do this in two passes - first a strict pass where we try
# to parse things semi-sensibly.  If that fails, there is a second pass
# where we try to cope with certain types of weirdness we've seen in the
# wild. The wild can be pretty wild.
#
# This parser is NOT fully RFC2822 compliant - in particular it will get
# confused by nested comments (see FIXME in tests below).
#
import sys
import traceback

from mailpile.mailutils import AddressHeaderParser as AHP


ahp_tests = AHP(AHP.TEST_HEADER_DATA)
print '_tokens: %s' % ahp_tests._tokens
print '_groups: %s' % ahp_tests._groups
print '%s' % ahp_tests
print 'normalized: %s' % ahp_tests.normalized()


headers, header, inheader = {}, None, False
for line in sys.stdin:
    if inheader:
        if line in ('\n', '\r\n'):
            for hdr in ('from', 'to', 'cc'):
                val = headers.get(hdr, '').replace('\n', ' ').strip()
                if val:
                    try:
                        nv = AHP(val, _raise=True).normalized()
Esempio n. 12
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:
Esempio n. 13
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 = session.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. We'll use the system default if
        # nothing is found, but hopefully we'll find an address we
        # recognize in one of the headers.
        from_address = (session.config.prefs.default_email
                        or session.config.profiles[0].email)
        profile_emails = [p.email for p in session.config.profiles if p.email]
        for src in (ref_from, ref_to, ref_cc):
            matches = [s for s in src if s.address in profile_emails]
            if matches:
                from_address = matches[0].address
                break
        result['from'] = ahp.normalized(addresses=[
            AddressInfo(p.email, p.name) for p in session.config.profiles
            if p.email == from_address
        ],
                                        force_name=True)

        def addresses(addrs, exclude=[]):
            alist = [from_address] + [a.address for a in exclude]
            return ahp.normalized_addresses(addresses=[
                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
            ],
                                            with_keys=True,
                                            force_name=True)

        # If only replying to messages sent from chosen from, then this is
        # a follow-up or clarification, so just use the same headers.
        if len([e for e in ref_from
                if e.address == from_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