Exemple #1
0
def test_prepend_headerfields():
    # we can inject headerfields
    msg = Parser(policy=default_policy).parsestr(
        "To: foo\nSubject: bar\n\nMeet at dawni\n")
    msg.add_header("X-Foo", "baz")
    result = pgp.prepend_header_fields(msg, [("To", "foo"), ("From", "bar")])
    assert result.keys() == ["From", "To", "Subject", "X-Foo"]
Exemple #2
0
    def header(self):
        """
        Returns the message header, if it exists. Otherwise it will generate one.
        """
        try:
            return self._header
        except AttributeError:
            headerText = self._getStringStream('__substg1.0_007D')
            if headerText is not None:
                self._header = EmailParser().parsestr(headerText)
                self._header['date'] = self.date
            else:
                logger.info(
                    'Header is empty or was not found. Header will be generated from other streams.'
                )
                header = EmailParser().parsestr('')
                header.add_header('Date', self.date)
                header.add_header('From', self.sender)
                header.add_header('To', self.to)
                header.add_header('Cc', self.cc)
                header.add_header('Message-Id', self.message_id)
                # TODO find authentication results outside of header
                header.add_header('Authentication-Results', None)

                self._header = header
            return self._header
Exemple #3
0
def load_mail_from_file(mail_file, enforceUniqueMessageId=False):
    mailset_dir = pkg_resources.resource_filename('test.unit.fixtures', 'mailset')
    mail_file = os.path.join(mailset_dir, 'new', mail_file)
    with open(mail_file) as f:
        mail = Parser().parse(f)

    if enforceUniqueMessageId:
        mail.add_header('Message-Id', make_msgid())

    return mail
def load_mail_from_file(mail_file, enforceUniqueMessageId=False):
    mailset_dir = pkg_resources.resource_filename('test.unit.fixtures',
                                                  'mailset')
    mail_file = os.path.join(mailset_dir, 'new', mail_file)
    with open(mail_file) as f:
        mail = Parser().parse(f)

    if enforceUniqueMessageId:
        mail.add_header('Message-Id', make_msgid())

    return mail
Exemple #5
0
    def testStripLeapHeaders(self):
        ENC_HEADER = "fake encryption header"
        SIG_HEADER = "fake signature header"

        message = Parser().parsestr(self.EMAIL)
        message.add_header("X-Leap-Encryption", ENC_HEADER)
        message.add_header("X-Leap-Signature", SIG_HEADER)
        self.fetcher._add_message_locally = Mock()

        def check_headers(_):
            self.assertTrue(self.fetcher._add_message_locally.called,
                            "The message was not added to soledad")
            _, data = self.fetcher._add_message_locally.call_args[0][0]
            msg = Parser().parsestr(data)
            self.assertNotEqual(msg.get('X-Leap-Encryption', ''), ENC_HEADER)
            self.assertNotEqual(msg.get('X-Leap-Signature', ''), SIG_HEADER)

        d = self._do_fetch(message.as_string())
        d.addCallback(check_headers)
        return d
    def testExtractOpenPGPHeaderInvalidUrl(self):
        """
        Test the OpenPGP header key extraction
        """
        KEYURL = "https://someotherdomain.com/key.txt"
        OpenPGP = "id=12345678; url=\"%s\"; preference=signencrypt" % (KEYURL,)

        message = Parser().parsestr(self.EMAIL)
        message.add_header("OpenPGP", OpenPGP)
        self.fetcher._keymanager.fetch_key = Mock()

        def fetch_key_called(ret):
            self.assertFalse(self.fetcher._keymanager.fetch_key.called)

        d = self._create_incoming_email(message.as_string())
        d.addCallback(
            lambda email:
            self._mock_soledad_get_from_index(fields.JUST_MAIL_IDX, [email]))
        d.addCallback(lambda _: self.fetcher.fetch())
        d.addCallback(fetch_key_called)
        return d
    def testExtractOpenPGPHeaderInvalidUrl(self):
        """
        Test the OpenPGP header key extraction
        """
        KEYURL = "https://someotherdomain.com/key.txt"
        OpenPGP = "id=12345678; url=\"%s\"; preference=signencrypt" % (KEYURL,)

        message = Parser().parsestr(self.EMAIL)
        message.add_header("OpenPGP", OpenPGP)
        self.fetcher._keymanager.fetch_key = Mock()

        def fetch_key_called(ret):
            self.assertFalse(self.fetcher._keymanager.fetch_key.called)

        d = self._create_incoming_email(message.as_string())
        d.addCallback(
            lambda email:
            self._mock_soledad_get_from_index(fields.JUST_MAIL_IDX, [email]))
        d.addCallback(lambda _: self.fetcher.fetch())
        d.addCallback(fetch_key_called)
        return d
Exemple #8
0
    def _handleTcpStream(self, tcp):
        """Parser call-back for processing data streams."""
        # Collect time and IP metadata
        ((src, sport), (dst, dport)) = tcp.addr

        # Grab data for every SMTP session
        if tcp.nids_state == nids.NIDS_JUST_EST:
            if sport == 25 or dport == 25:
                tcp.client.collect = 1
                tcp.server.collect = 1

        # Wait until the SMTP session completes before processing
        elif tcp.nids_state == nids.NIDS_DATA:
            tcp.discard(0)

        # Process SMTP session data
        elif tcp.nids_state in self.end_states:

            # We're only interested in the server side of the traffic
            server_data = tcp.server.data[:tcp.server.count]

            # Python's email parser module doesn't seem to like ESMTP
            # formatted messages, so we'll skip the EHLO -> DATA header,
            # parse from the first 'Received:' value, and add the ESMTP
            # header values back in later.
            f = server_data.find("Received:")
            message = Parser().parsestr(server_data[f:])

            # Add ESMTP headers back into the email message object
            esmtp_start = server_data.find("EHLO")
            esmtp_end = server_data.find("DATA")
            if esmtp_start >= 0:
                esmtp_headers = server_data[esmtp_start:esmtp_end]
                for line in esmtp_headers.splitlines():
                    if "EHLO" not in line:
                        (name, value) = line.split(":")
                        message.add_header(name, value)

            self.messages.append(message)
Exemple #9
0
    def _handleTcpStream(self, tcp):
        """Parser call-back for processing data streams."""
        # Collect time and IP metadata
        ((src, sport), (dst, dport)) = tcp.addr

        # Grab data for every SMTP session
        if tcp.nids_state == nids.NIDS_JUST_EST:
            if sport == 25 or dport == 25:
                tcp.client.collect = 1
                tcp.server.collect = 1

        # Wait until the SMTP session completes before processing
        elif tcp.nids_state == nids.NIDS_DATA:
            tcp.discard(0)

        # Process SMTP session data
        elif tcp.nids_state in self.end_states:

            # We're only interested in the server side of the traffic
            server_data = tcp.server.data[:tcp.server.count]

            # Python's email parser module doesn't seem to like ESMTP
            # formatted messages, so we'll skip the EHLO -> DATA header,
            # parse from the first 'Received:' value, and add the ESMTP
            # header values back in later.
            f = server_data.find("Received:")
            message = Parser().parsestr(server_data[f:])

            # Add ESMTP headers back into the email message object
            esmtp_start = server_data.find("EHLO")
            esmtp_end = server_data.find("DATA")
            if esmtp_start >= 0:
                esmtp_headers = server_data[esmtp_start:esmtp_end]
                for line in esmtp_headers.splitlines():
                    if "EHLO" not in line:
                        (name, value) = line.split(":")
                        message.add_header(name, value)

            self.messages.append(message)
    def testExtractOpenPGPHeader(self):
        """
        Test the OpenPGP header key extraction
        """
        KEYURL = "https://leap.se/key.txt"
        OpenPGP = "id=12345678; url=\"%s\"; preference=signencrypt" % (KEYURL,)

        message = Parser().parsestr(self.EMAIL)
        message.add_header("OpenPGP", OpenPGP)
        self.fetcher._keymanager.fetch_key = Mock(
            return_value=defer.succeed(None))

        def fetch_key_called(ret):
            self.fetcher._keymanager.fetch_key.assert_called_once_with(
                ADDRESS_2, KEYURL, OpenPGPKey)

        d = self._create_incoming_email(message.as_string())
        d.addCallback(
            lambda email:
            self._mock_soledad_get_from_index(fields.JUST_MAIL_IDX, [email]))
        d.addCallback(lambda _: self.fetcher.fetch())
        d.addCallback(fetch_key_called)
        return d
    def testExtractOpenPGPHeader(self):
        """
        Test the OpenPGP header key extraction
        """
        KEYURL = "https://leap.se/key.txt"
        OpenPGP = "id=12345678; url=\"%s\"; preference=signencrypt" % (KEYURL,)

        message = Parser().parsestr(self.EMAIL)
        message.add_header("OpenPGP", OpenPGP)
        self.fetcher._keymanager.fetch_key = Mock(
            return_value=defer.succeed(None))

        def fetch_key_called(ret):
            self.fetcher._keymanager.fetch_key.assert_called_once_with(
                ADDRESS_2, KEYURL, OpenPGPKey)

        d = self._create_incoming_email(message.as_string())
        d.addCallback(
            lambda email:
            self._mock_soledad_get_from_index(fields.JUST_MAIL_IDX, [email]))
        d.addCallback(lambda _: self.fetcher.fetch())
        d.addCallback(fetch_key_called)
        return d
Exemple #12
0
    def header(self):
        """
        Returns the message header, if it exists. Otherwise it will generate one.
        """
        try:
            return self._header
        except AttributeError:
            headerText = self._getStringStream('__substg1.0_007D')
            if headerText is not None:
                self._header = EmailParser().parsestr(headerText)
                self._header['date'] = self.date
            else:
                logger.info('Header is empty or was not found. Header will be generated from other streams.')
                header = EmailParser().parsestr('')
                header.add_header('Date', self.date)
                header.add_header('From', self.sender)
                header.add_header('To', self.to)
                header.add_header('Cc', self.cc)
                header.add_header('Message-Id', self.message_id)
                # TODO find authentication results outside of header
                header.add_header('Authentication-Results', None)

                self._header = header
            return self._header
Exemple #13
0
def execute(*args, **kw):
    if not os.path.isdir(mybasepath):
        os.makedirs(mybasepath)

    for stage in ['incoming', 'ACCEPT' ]:
        if not os.path.isdir(os.path.join(mybasepath, stage)):
            os.makedirs(os.path.join(mybasepath, stage))

    # TODO: Test for correct call.
    filepath = args[0]

    if kw.has_key('stage'):
        log.debug(_("Issuing callback after processing to stage %s") % (kw['stage']), level=8)
        log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8)
        if hasattr(modules, 'cb_action_%s' % (kw['stage'])):
            log.debug(_("Attempting to execute cb_action_%s()") % (kw['stage']), level=8)
            exec('modules.cb_action_%s(%r, %r)' % (kw['stage'],'optout',filepath))
            return

    log.debug(_("Executing module footer for %r, %r") % (args, kw), level=8)

    new_filepath = os.path.join('/var/spool/pykolab/wallace/footer/incoming', os.path.basename(filepath))
    os.rename(filepath, new_filepath)
    filepath = new_filepath

    # parse message headers
    # @TODO: make sure we can use True as the 2nd argument here
    message = Parser().parse(open(filepath, 'r'), True)

    # Possible footer answers are limited to ACCEPT only
    answers = [ 'ACCEPT' ]

    footer = {}

    footer_html_file = conf.get('wallace', 'footer_html')
    footer_text_file = conf.get('wallace', 'footer_text')

    if not os.path.isfile(footer_text_file) and not os.path.isfile(footer_html_file):
        exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','footer', filepath))
        return
        
    if os.path.isfile(footer_text_file):
        footer['plain'] = open(footer_text_file, 'r').read()

    if not os.path.isfile(footer_html_file):
        footer['html'] = '<p>' + self.footer['plain'] + '</p>'
    else:
        footer['html'] = open(footer_html_file, 'r').read()
        if footer['html'] == "":
            footer['html'] = '<p>' + self.footer['plain'] + '</p>'

    if footer['plain'] == "" and footer['html'] == "<p></p>":
        exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','footer', filepath))
        return
        
    footer_added = False

    try:
        _footer_added = message.get("X-Wallace-Footer")
    except:
        pass

    if _footer_added == "YES":
        exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','footer', filepath))
        return

    if message.is_multipart():
        if message.get_content_type() == "multipart/alternative":
            log.debug("The message content type is multipart/alternative.")

        for part in message.walk():
            disposition = None

            try:
                content_type = part.get_content_type()
            except:
                continue

            try:
                disposition = part.get("Content-Disposition")
            except:
                pass

            if not disposition == None:
                continue

            if content_type == "text/plain":
                content = part.get_payload()
                content += "\n\n--\n%s" % (footer['plain'])
                part.set_payload(content)
                footer_added = True

            elif content_type == "text/html":
                content = part.get_payload()
                content += "\n<!-- footer appended by Wallace -->\n"
                content += "\n<html><body><hr />%s</body></html>\n" % (footer['html'])
                part.set_payload(content)
                footer_added = True

    else:
        # Check the main content-type.
        if message.get_content_type() == "text/html":
            content = message.get_payload()
            content += "\n<!-- footer appended by Wallace -->\n"
            content += "\n<html><body><hr />%s</body></html>\n" % (footer['html'])
            message.set_payload(content)
            footer_added = True

        else:
            content = message.get_payload()
            content += "\n\n--\n%s" % (footer['plain'])
            message.set_payload(content)
            footer_added = True

    if footer_added:
        log.debug("Footer attached.")
        message.add_header("X-Wallace-Footer", "YES")

    (fp, new_filepath) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/footer/ACCEPT")
    os.write(fp, message.as_string())
    os.close(fp)
    os.unlink(filepath)

    exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','footer', new_filepath))
Exemple #14
0
def execute(*args, **kw):
    if not os.path.isdir(mybasepath):
        os.makedirs(mybasepath)

    for stage in ['incoming', 'ACCEPT']:
        if not os.path.isdir(os.path.join(mybasepath, stage)):
            os.makedirs(os.path.join(mybasepath, stage))

    # TODO: Test for correct call.
    filepath = args[0]

    if kw.has_key('stage'):
        log.debug(_("Issuing callback after processing to stage %s") %
                  (kw['stage']),
                  level=8)
        log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8)
        if hasattr(modules, 'cb_action_%s' % (kw['stage'])):
            log.debug(_("Attempting to execute cb_action_%s()") %
                      (kw['stage']),
                      level=8)
            exec('modules.cb_action_%s(%r, %r)' %
                 (kw['stage'], 'gpgencrypt', filepath))

    log.debug(_("Executing module gpgencrypt for %r, %r") % (args, kw),
              level=8)

    new_filepath = os.path.join(
        '/var/spool/pykolab/wallace/gpgencrypt/incoming',
        os.path.basename(filepath))

    if not filepath == new_filepath:
        log.debug("Renaming %r to %r" % (filepath, new_filepath))
        os.rename(filepath, new_filepath)
        filepath = new_filepath

    # parse message headers
    # @TODO: make sure we can use True as the 2nd argument here
    message = Parser().parse(open(filepath, 'r'), True)

    # Possible gpgencrypt answers are limited to ACCEPT only
    answers = ['ACCEPT']

    # from Mail::GnuPG.is_encrypted
    #
    #sub is_encrypted {
    #  my ($self,$entity) = @_;
    #  return 1
    #    if (($entity->effective_type =~ m!multipart/encrypted!)
    #    ||
    #    ($entity->as_string =~ m!^-----BEGIN PGP MESSAGE-----!m));
    #  return 0;
    #}

    message_already_encrypted = False

    for part in message.walk():
        if part.get_content_type() in ["application/pgp-encrypted"]:
            message_already_encrypted = True
            log.debug(
                _("Message is already encrypted (app/pgp-enc content-type)"),
                level=8)

    if message.get_content_type() in ["multipart/encrypted"]:
        message_already_encrypted = True
        log.debug(_("Message already encrypted by main content-type header"),
                  level=8)

    if message_already_encrypted:
        return filepath

    try:
        # What are recipient addresses to encrypt to (bitmask)?
        # 1 - organization key
        # 2 - envelope to
        # 4 - to
        # 8 - cc
        # 16 - resent-to
        # 32 - resent-cc
        encrypt_to_rcpts = conf.get('wallace', 'gpgencrypt_to_rcpts')
        if encrypt_to_rcpts == None:
            encrypt_to_rcpts = 14
        else:
            encrypt_to_rcpts = (int)(encrypt_to_rcpts)

        # Only encrypt to keys that are trusted
        strict_crypt = conf.get('wallace', 'gpgencrypt_strict')
        if strict_crypt == None:
            strict_crypt = False

        # Organization key ID
        if encrypt_to_rcpts & 1:
            encrypt_to_org = conf.get('wallace', 'gpgencrypt_to_org_key')
            if encrypt_to_org == None and encrypt_to_rcpts & 1:
                if strict_crypt:
                    log.error(
                        _("Configured to encrypt to a key not configured, and strict policy enabled. Bailing out."
                          ))
                    modules.cb_action_REJECT('gpgencrypt', filepath)
                else:
                    log.error(
                        _("Configured to encrypt to a key not configured, but continuing anyway (see 'gpgencrypt_strict')."
                          ))
        else:
            encrypt_to_org = []

        # Bounce the message if encryption fails?
        force_crypt = conf.get('wallace', 'gpgencrypt_force')
        if force_crypt == None:
            force_crypt = False

        # Retrieve keys from remote server(s) automatically?
        retrieve_keys = conf.get('wallace', 'gpgencrypt_retrieve_keys')
        if retrieve_keys == None:
            retrieve_keys = False

        if retrieve_keys:
            gpgserver = conf.get('wallace', 'gpgencrypt_server')
            if gpgserver == None:
                gpgserver = 'pgp.mit.edu'

        encrypt_to = []
        if encrypt_to_rcpts & 2:
            encrypt_to.extend(message.get_all('X-Kolab-To', []))

        if encrypt_to_rcpts & 4:
            encrypt_to.extend(message.get_all('to', []))

        if encrypt_to_rcpts & 8:
            encrypt_to.extend(message.get_all('cc', []))

        if encrypt_to_rcpts & 16:
            encrypt_to.extend(message.get_all('resent-to', []))

        if encrypt_to_rcpts & 32:
            encrypt_to.extend(message.get_all('resent-cc', []))

        recipients = [
            address for displayname, address in getaddresses(encrypt_to)
        ]

        log.debug(_("Recipients: %r") % (recipients))

        # Split between recipients we can encrypt for/to, and ones we can not
        encrypt_rcpts = []
        nocrypt_rcpts = []

        gpg = gnupg.GPG(gnupghome='/var/lib/kolab/.gnupg',
                        verbose=conf.debuglevel > 8)
        gpg.encoding = 'utf-8'

        local_keys = gpg.list_keys()
        log.debug(_("Current keys: %r") % (local_keys), level=8)

        for recipient in recipients:
            key_local = False

            log.debug(_("Retrieving key for recipient: %r") % (recipient))

            for key in local_keys:
                for address in [
                        x for x in [
                            address for displayname, address in getaddresses(
                                key['uids'])
                        ] if x == recipient
                ]:
                    log.debug(_("Found matching address %r") % (address))
                    key_local = key['keyid']

            if key_local == False:
                if retrieve_keys:
                    remote_keys = gpg.search_keys(recipient, gpgserver)
                    if len(remote_keys) == 1:
                        for address in [
                                x for x in [
                                    address for displayname, address in
                                    getaddresses(remote_keys[0]['uids'])
                                ] if x == recipient
                        ]:
                            log.debug(
                                _("Found matching address %r in remote keys") %
                                (address))
                            gpg.recv_keys(gpgserver, remote_keys[0]['keyid'])
                            local_keys = gpg.list_keys()
                    else:
                        nocrypt_rcpts.append(recipient)

            for key in local_keys:
                for address in [
                        x for x in [
                            address for displayname, address in getaddresses(
                                key['uids'])
                        ] if x == recipient
                ]:
                    log.debug(_("Found matching address %r") % (address))
                    key_local = key['keyid']
            if not key_local == False:
                encrypt_rcpts.append(key_local)

        payload = message.get_payload()
        #print "payload:", payload
        if len(encrypt_rcpts) < 1:
            return filepath

        if "multipart" in message.get_content_type():

            log.debug(_(
                "Mime Message - we need to build multipart/encrypted structure"
            ),
                      level=8)

            msg = message
            enc_mime_message = pgp_mime(msg, encrypt_rcpts)

            message = enc_mime_message

        else:

            log.debug(_("No Mime Message - encypt plain"), level=8)

            encrypted_data = gpg.encrypt(payload,
                                         encrypt_rcpts,
                                         always_trust=True)
            encrypted_string = str(encrypted_data)

            message.set_payload(encrypted_string)

            message.add_header('X-wallace-gpg-encrypted', 'true')

        (fp, new_filepath) = tempfile.mkstemp(
            dir="/var/spool/pykolab/wallace/gpgencrypt/ACCEPT")
        os.write(fp, message.as_string())
        os.close(fp)
        os.unlink(filepath)

        exec('modules.cb_action_%s(%r, %r)' %
             ('ACCEPT', 'gpgencrypt', new_filepath))
    except Exception, errmsg:
        log.error(_("An error occurred: %r") % (errmsg))
        if conf.debuglevel > 8:
            import traceback
            traceback.print_exc()
Exemple #15
0
                    mail.attach(part)
                    os.remove(f)

                mail_files[0] = (mail_files[0][0], mail)
            else:
                print "Can't decide how to bundle the %s non-mail files among the %s mails. Skip the folder..." % (len(non_mail_files), len(mail_files))
                continue

        parent_folder_name = os.path.split(path)[-1]
        parent_folders = path.split(os.sep)[::-1]

        for (filepath, mail) in mail_files:

            # The mail was saved from an IMP account
            if mail.get('X-Mailer-Version', None) == 'v3.57 (r)' and not mail.get('From', None) and not mail.get('Date', None):
                mail.add_header('From', '*****@*****.**')
                # Get the main body of the mail
                mail_body = None
                if mail.is_multipart():
                    for payload in mail.get_payload():
                        if isinstance(payload, email.mime.text.MIMEText):
                            mail_body = payload.get_payload()
                            break
                else:
                    mail_body = mail.get_payload()
                assert type(mail_body) == type('')
                # Find the hard coded date
                date = None
                # Normalize line endings
                mail_body = mail_body.replace('\r\n', '\n')
                cleaned_body = ''
Exemple #16
0
                mail_files[0] = (mail_files[0][0], mail)
            else:
                print "Can't decide how to bundle the %s non-mail files among the %s mails. Skip the folder..." % (
                    len(non_mail_files), len(mail_files))
                continue

        parent_folder_name = os.path.split(path)[-1]
        parent_folders = path.split(os.sep)[::-1]

        for (filepath, mail) in mail_files:

            # The mail was saved from an IMP account
            if mail.get(
                    'X-Mailer-Version', None) == 'v3.57 (r)' and not mail.get(
                        'From', None) and not mail.get('Date', None):
                mail.add_header('From', '*****@*****.**')
                # Get the main body of the mail
                mail_body = None
                if mail.is_multipart():
                    for payload in mail.get_payload():
                        if isinstance(payload, email.mime.text.MIMEText):
                            mail_body = payload.get_payload()
                            break
                else:
                    mail_body = mail.get_payload()
                assert type(mail_body) == type('')
                # Find the hard coded date
                date = None
                # Normalize line endings
                mail_body = mail_body.replace('\r\n', '\n')
                cleaned_body = ''
def header_check (db, cl, nodeid, new_values) :
    """ Check header of new messages and determine original customer
        from that header -- only if sender is the support special
        account (any account with system status).  If send_to_customer
        flag is set *and* account is not a system account, munge
        the headers and add X-ROUNDUP-TO and X-ROUNDUP-CC headers.
    """
    send_to_customer = False
    # Be sure to alway set send_to_customer to False!
    if 'send_to_customer' in new_values :
        send_to_customer = new_values ['send_to_customer']
        new_values ['send_to_customer'] = False
    newmsgs = new_values.get ('messages')
    if not newmsgs :
        return
    newmsgs = set (newmsgs)
    if nodeid :
        oldmsgs = set (cl.get (nodeid, 'messages'))
    else :
        oldmsgs = set ()
    system  = db.user_status.lookup ('system')
    cemail  = db.contact_type.lookup ('Email')
    for msgid in newmsgs.difference (oldmsgs) :
        msg    = db.msg.getnode (msgid)
        h      = None
        if msg.header :
            h = Parser ().parsestr (msg.header, headersonly = True)
        else :
            h = Message ()
        if db.user.get (msg.author, 'status') == system :
            frm  = fix_emails (h.get_all ('From'))
            subj = header_utf8 (h.get_all ('Subject') [0])
            if  (   frm
                and 'customer' not in new_values
                and 'emails' not in new_values
                ) :
                cc  = {}
                if not nodeid :
                    # use only first 'From' address (there shouldn't be more)
                    rn, mail = getaddresses (frm) [0]
                    # the *to* address in this mail is the support user we
                    # want as a from-address for future mails *to* this user
                    autad = None
                    hto = fix_emails (h.get_all ('To'))
                    if hto :
                        torn, autad = getaddresses (hto) [0]
                        if not autad.startswith ('support') :
                            autad = None
                    c = find_or_create_contact \
                        (db, mail, rn, frm = autad, subject = subj)
                    cust  = new_values ['customer'] = \
                        db.contact.get (c, 'customer')
                    new_values ['emails'] = [c]
                else :
                    supi = cl.getnode (nodeid)
                    cust = supi.customer
                    new_values ['emails']    = supi.emails
                    new_values ['cc_emails'] = supi.cc_emails
                    if supi.cc :
                        cc = dict.fromkeys \
                            (x.strip ().lower () for x in supi.cc.split (','))
                # Parse To and CC headers to find more customer email
                # addresses. Check if these contain the same domain
                # part as the From.
                ccs = h.get_all ('CC') or []
                tos = h.get_all ('To') or []
                if nodeid :
                    tos.extend (frm)
                cfrm = db.customer.get (cust, 'fromaddress')
                alltocc = dict.fromkeys (new_values ['emails'])
                if 'cc_emails' in new_values :
                    alltocc.update (dict.fromkeys (new_values ['cc_emails']))
                for addrs, field in ((tos, 'emails'), (ccs, 'cc_emails')) :
                    addrs = fix_emails (addrs)
                    for rn, mail in getaddresses (addrs) :
                        if mail == cfrm :
                            continue
                        c = find_or_create_contact \
                            (db, mail, rn, customer = cust)
                        if c :
                            if field not in new_values :
                                new_values [field] = []
                            if c not in alltocc :
                                new_values [field].append (c)
                                alltocc [c] = 1
                        elif uidFromAddress (db, (rn, mail), create = 0) :
                            # ignore internal addresses
                            pass
                        else :
                            cc [mail.lower ()] = 1
                if cc :
                    new_values ['cc'] = ', '.join (cc.keys ())
        else :
            if send_to_customer :
                mails = []
                cc = []
                if 'emails' in new_values :
                    mails = new_values ['emails']
                elif nodeid :
                    mails = cl.get (nodeid, 'emails')
                if 'cc_emails' in new_values :
                    mcc = new_values ['cc_emails']
                elif nodeid :
                    mcc = cl.get (nodeid, 'cc_emails')
                mails = mails or []
                mcc   = mcc or []
                mails = (db.contact.get (x, 'contact') for x in mails)
                mcc   = (db.contact.get (x, 'contact') for x in mcc)
                if 'cc' in new_values :
                    cc = new_values ['cc']
                elif nodeid :
                    cc = cl.get (nodeid, 'cc')
                m  = ','.join (mails)
                mc = ','.join (mcc)
                if mc :
                    if cc :
                        mc = ','.join ((mc, cc))
                else :
                    mc = cc
                if not m and not mc :
                    raise Reject, \
                        _ ("Trying to send to customer with empty CC and "
                           "without configured contact-email for customer"
                          )
                if m :
                    h.add_header ('X-ROUNDUP-TO', m)
                if mc :
                    h.add_header ('X-ROUNDUP-CC', mc)
                if 'bcc' in new_values :
                    bcc = new_values ['bcc']
                elif nodeid :
                    bcc = cl.get (nodeid, 'bcc')
                if bcc :
                    h.add_header ('X-ROUNDUP-BCC', bcc)
                # Time-Stamp of first reply to customer
                if not nodeid or not cl.get (nodeid, 'first_reply') :
                    new_values ['first_reply'] = Date ('.')
        h = h.as_string ()
        if h != '\n' and h != msg.header :
            db.msg.set (msgid, header = h)
Exemple #18
0
    def construct(cls, raw_headers=None, from_email=None, to=None, cc=None, subject=None, headers=None,
                  text=None, text_charset='utf-8', html=None, html_charset='utf-8',
                  attachments=None):
        """
        Returns a new AnymailInboundMessage constructed from params.

        This is designed to handle the sorts of email fields typically present
        in ESP parsed inbound messages. (It's not a generalized MIME message constructor.)

        :param raw_headers: {str|None} base (or complete) message headers as a single string
        :param from_email: {str|None} value for From header
        :param to: {str|None} value for To header
        :param cc: {str|None} value for Cc header
        :param subject: {str|None} value for Subject header
        :param headers: {sequence[(str, str)]|mapping|None} additional headers
        :param text: {str|None} plaintext body
        :param text_charset: {str} charset of plaintext body; default utf-8
        :param html: {str|None} html body
        :param html_charset: {str} charset of html body; default utf-8
        :param attachments: {list[MIMEBase]|None} as returned by construct_attachment
        :return: {AnymailInboundMessage}
        """
        if raw_headers is not None:
            msg = Parser(cls, policy=default_policy).parsestr(raw_headers, headersonly=True)
            msg.set_payload(None)  # headersonly forces an empty string payload, which breaks things later
        else:
            msg = cls()

        if from_email is not None:
            del msg['From']  # override raw_headers value, if any
            msg['From'] = from_email
        if to is not None:
            del msg['To']
            msg['To'] = to
        if cc is not None:
            del msg['Cc']
            msg['Cc'] = cc
        if subject is not None:
            del msg['Subject']
            msg['Subject'] = subject
        if headers is not None:
            try:
                header_items = headers.items()  # mapping
            except AttributeError:
                header_items = headers  # sequence of (key, value)
            for name, value in header_items:
                msg.add_header(name, value)

        # For simplicity, we always build a MIME structure that could support plaintext/html
        # alternative bodies, inline attachments for the body(ies), and message attachments.
        # This may be overkill for simpler messages, but the structure is never incorrect.
        del msg['MIME-Version']  # override raw_headers values, if any
        del msg['Content-Type']
        msg['MIME-Version'] = '1.0'
        msg['Content-Type'] = 'multipart/mixed'

        related = cls()  # container for alternative bodies and inline attachments
        related['Content-Type'] = 'multipart/related'
        msg.attach(related)

        alternatives = cls()  # container for text and html bodies
        alternatives['Content-Type'] = 'multipart/alternative'
        related.attach(alternatives)

        if text is not None:
            part = cls()
            part['Content-Type'] = 'text/plain'
            part.set_payload(text, charset=text_charset)
            alternatives.attach(part)
        if html is not None:
            part = cls()
            part['Content-Type'] = 'text/html'
            part.set_payload(html, charset=html_charset)
            alternatives.attach(part)

        if attachments is not None:
            for attachment in attachments:
                if attachment.is_inline_attachment():
                    related.attach(attachment)
                else:
                    msg.attach(attachment)

        return msg