def convert_encrypted_mime_message(crypto_message, ciphertext, from_user,
                                   to_user):
    '''
        Convert a MIME message that has been signed or encrypted, and creating a new
        plain text message with the payload the encrypted/signed original message. This reduces the
        metadata someone can collect, but it does require the receiving end decrypt the
        message and create a new readable message from the original message.
    '''
    def copy_item_from_original(msg, keyword):
        value = crypto_message.get_email_message().get_header(keyword)
        if value is not None:
            msg.__setitem__(keyword, value)

    if ciphertext is not None and len(ciphertext) > 0:
        # set up the body parts
        parts = []
        parts.append(
            MIMEApplication(mime_constants.PGP_MIME_VERSION_FIELD,
                            mime_constants.PGP_SUB_TYPE, encode_7or8bit))
        parts.append(
            MIMEApplication(ciphertext, mime_constants.OCTET_STREAM_SUB_TYPE,
                            encode_7or8bit))

        boundary = 'Part{}{}--'.format(random(), random())
        charset, __ = get_charset(
            crypto_message.get_email_message().get_message())
        params = {
            mime_constants.PROTOCOL_KEYWORD: mime_constants.PGP_TYPE,
            mime_constants.CHARSET_KEYWORD: charset,
        }
        msg = MIMEMultipart(mime_constants.ENCRYPTED_SUB_TYPE, boundary, parts,
                            **params)
        log_message("part's content type: {}".format(msg.get_content_type()))

        # configure the header
        msg.__setitem__(mime_constants.FROM_KEYWORD, from_user)
        msg.__setitem__(mime_constants.TO_KEYWORD, to_user)
        msg.__setitem__(constants.PGP_ENCRYPTED_CONTENT_TYPE,
                        mime_constants.MULTIPART_MIXED_TYPE)
        copy_item_from_original(msg, mime_constants.MESSAGE_ID_KEYWORD)
        copy_item_from_original(msg, mime_constants.SUBJECT_KEYWORD)
        copy_item_from_original(msg, mime_constants.DATE_KEYWORD)

        crypto_message.set_email_message(EmailMessage(msg))
        crypto_message.add_public_key_to_header(from_user)
        crypto_message.set_filtered(True)
        crypto_message.set_crypted(True)
Пример #2
0
    def bind(self, param_values, **kw_param_values):
        """Bind the definition to parameter values, creating a document.

        :return: A 2-tuple (media_type, document).
        """
        definition = self.resolve_definition()
        params = definition.params(self.resource)
        validated_values = self.validate_param_values(params, param_values,
                                                      **kw_param_values)
        media_type = self.media_type
        if media_type == 'application/x-www-form-urlencoded':
            doc = urlencode(sorted(validated_values.items()))
        elif media_type == 'multipart/form-data':
            outer = MIMEMultipart()
            outer.set_type('multipart/form-data')
            missing = object()
            for param in params:
                value = validated_values.get(param.name, missing)
                if value is not missing:
                    if param.type == 'binary':
                        maintype, subtype = 'application', 'octet-stream'
                        params = {}
                    else:
                        maintype, subtype = 'text', 'plain'
                        params = {'charset': 'utf-8'}
                    inner = MIMENonMultipart(maintype, subtype, **params)
                    inner.set_payload(value)
                    inner['Content-Disposition'] = ('form-data; name="%s"' %
                                                    param.name)
                    outer.attach(inner)
            doc = str(outer)
            # Chop off the 'From' line, which only makes sense in an
            # email.  (Python 3 does not include it.)
            if doc.startswith('From '):
                doc = doc[doc.find('\n') + 1:]
            media_type = (outer.get_content_type() +
                          '; boundary="%s"' % outer.get_boundary())
        elif media_type == 'application/json':
            doc = json.dumps(validated_values)
        else:
            raise ValueError("Unsupported media type: '%s'" % media_type)
        return media_type, doc
Пример #3
0
    def bind(self, param_values, **kw_param_values):
        """Bind the definition to parameter values, creating a document.

        :return: A 2-tuple (media_type, document).
        """
        definition = self.resolve_definition()
        params = definition.params(self.resource)
        validated_values = self.validate_param_values(
            params, param_values, **kw_param_values)
        media_type = self.media_type
        if media_type == 'application/x-www-form-urlencoded':
            doc = urlencode(sorted(validated_values.items()))
        elif media_type == 'multipart/form-data':
            outer = MIMEMultipart()
            outer.set_type('multipart/form-data')
            missing = object()
            for param in params:
                value = validated_values.get(param.name, missing)
                if value is not missing:
                    if param.type == 'binary':
                        maintype, subtype = 'application', 'octet-stream'
                        params = {}
                    else:
                        maintype, subtype = 'text', 'plain'
                        params = {'charset' : 'utf-8'}
                    inner = MIMENonMultipart(maintype, subtype, **params)
                    inner.set_payload(value)
                    inner['Content-Disposition'] = (
                        'form-data; name="%s"' % param.name)
                    outer.attach(inner)
            doc = str(outer)
            # Chop off the 'From' line, which only makes sense in an
            # email.  (Python 3 does not include it.)
            if doc.startswith('From '):
                doc = doc[doc.find('\n')+1:]
            media_type = (outer.get_content_type() +
                          '; boundary="%s"' % outer.get_boundary())
        elif media_type == 'application/json':
            doc = json.dumps(validated_values)
        else:
            raise ValueError("Unsupported media type: '%s'" % media_type)
        return media_type, doc
Пример #4
0
def convert_encrypted_mime_message(crypto_message, ciphertext, from_user, to_user):
    '''
        Convert a MIME message that has been signed or encrypted, and creating a new
        plain text message with the payload the encrypted/signed original message. This reduces the
        metadata someone can collect, but it does require the receiving end decrypt the
        message and create a new readable message from the original message.
    '''

    def copy_item_from_original(msg, keyword):
        value = crypto_message.get_email_message().get_header(keyword)
        if value is not None:
            msg.__setitem__(keyword, value)

    if ciphertext is not None and len(ciphertext) > 0:
        # set up the body parts
        parts = []
        parts.append(
           MIMEApplication(
             mime_constants.PGP_MIME_VERSION_FIELD, mime_constants.PGP_SUB_TYPE, encode_7or8bit))
        parts.append(
           MIMEApplication(ciphertext, mime_constants.OCTET_STREAM_SUB_TYPE, encode_7or8bit))

        boundary = 'Part{}{}--'.format(random(), random())
        charset, __ = get_charset(crypto_message.get_email_message().get_message())
        params = {mime_constants.PROTOCOL_KEYWORD:mime_constants.PGP_TYPE,
                  mime_constants.CHARSET_KEYWORD:charset,}
        msg = MIMEMultipart(mime_constants.ENCRYPTED_SUB_TYPE, boundary, parts, **params)
        log_message("part's content type: {}".format(msg.get_content_type()))

        # configure the header
        msg.__setitem__(mime_constants.FROM_KEYWORD, from_user)
        msg.__setitem__(mime_constants.TO_KEYWORD, to_user)
        msg.__setitem__(constants.PGP_ENCRYPTED_CONTENT_TYPE, mime_constants.MULTIPART_MIXED_TYPE)
        copy_item_from_original(msg, mime_constants.MESSAGE_ID_KEYWORD)
        copy_item_from_original(msg, mime_constants.SUBJECT_KEYWORD)
        copy_item_from_original(msg, mime_constants.DATE_KEYWORD)

        crypto_message.set_email_message(EmailMessage(msg))
        crypto_message.add_public_key_to_header(from_user)
        crypto_message.set_filtered(True)
        crypto_message.set_crypted(True)
Пример #5
0
class Mdn(object):
    """Class for handling AS2 MDNs. Includes functions for both
    parsing and building them.
    """

    def __init__(self, mdn_mode=None, digest_alg=None, mdn_url=None):
        self.message_id = None
        self.orig_message_id = None
        self.payload = None
        self.mdn_mode = mdn_mode
        self.digest_alg = digest_alg
        self.mdn_url = mdn_url

    @property
    def content(self):
        """Function returns the body of the mdn message as a byte string"""

        if self.payload:
            message_bytes = mime_to_bytes(
                self.payload, 0).replace(b'\n', b'\r\n')
            boundary = b'--' + self.payload.get_boundary().encode('utf-8')
            temp = message_bytes.split(boundary)
            temp.pop(0)
            return boundary + boundary.join(temp)
        else:
            return ''

    @property
    def headers(self):
        if self.payload:
            return dict(self.payload.items())
        else:
            return {}

    @property
    def headers_str(self):
        message_header = ''
        if self.payload:
            for k, v in self.headers.items():
                message_header += '{}: {}\r\n'.format(k, v)
        return message_header.encode('utf-8')

    def build(self, message, status, detailed_status=None):
        """Function builds and signs an AS2 MDN message.

        :param message: The received AS2 message for which this is an MDN.

        :param status: The status of processing of the received AS2 message.

        :param detailed_status:
            The optional detailed status of processing of the received AS2
            message. Used to give additional error info (default "None")

        """

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip('<').rstrip('>')
        self.orig_message_id = message.message_id

        # Set up the message headers
        mdn_headers = {
            'AS2-Version': AS2_VERSION,
            'ediint-features': EDIINT_FEATURES,
            'Message-ID': '<{}>'.format(self.message_id),
            'AS2-From': quote_as2name(message.headers.get('as2-to')),
            'AS2-To': quote_as2name(message.headers.get('as2-from')),
            'Date': email_utils.formatdate(localtime=True),
            'user-agent': 'pyAS2 Open Source AS2 Software'
        }

        # Set the confirmation text message here
        confirmation_text = MDN_CONFIRM_TEXT

        # overwrite with organization specific message
        if message.receiver and message.receiver.mdn_confirm_text:
            confirmation_text = message.receiver.mdn_confirm_text

        # overwrite with partner specific message
        if message.sender and message.sender.mdn_confirm_text:
            confirmation_text = message.sender.mdn_confirm_text

        if status != 'processed':
            confirmation_text = MDN_FAILED_TEXT

        self.payload = MIMEMultipart(
            'report', report_type='disposition-notification')

        # Create and attach the MDN Text Message
        mdn_text = email_message.Message()
        mdn_text.set_payload('%s\n' % confirmation_text)
        mdn_text.set_type('text/plain')
        del mdn_text['MIME-Version']
        encoders.encode_7or8bit(mdn_text)
        self.payload.attach(mdn_text)

        # Create and attache the MDN Report Message
        mdn_base = email_message.Message()
        mdn_base.set_type('message/disposition-notification')
        mdn_report = 'Reporting-UA: pyAS2 Open Source AS2 Software\n'
        mdn_report += 'Original-Recipient: rfc822; {}\n'.format(
            message.headers.get('as2-to'))
        mdn_report += 'Final-Recipient: rfc822; {}\n'.format(
            message.headers.get('as2-to'))
        mdn_report += 'Original-Message-ID: <{}>\n'.format(message.message_id)
        mdn_report += 'Disposition: automatic-action/' \
                      'MDN-sent-automatically; {}'.format(status)
        if detailed_status:
            mdn_report += ': {}'.format(detailed_status)
        mdn_report += '\n'
        if message.mic:
            mdn_report += 'Received-content-MIC: {}, {}\n'.format(
                message.mic.decode(), message.digest_alg)
        mdn_base.set_payload(mdn_report)
        del mdn_base['MIME-Version']
        encoders.encode_7or8bit(mdn_base)
        self.payload.attach(mdn_base)

        # logger.debug('MDN for message %s created:\n%s' % (
        #     message.message_id, mdn_base.as_string()))

        # Sign the MDN if it is requested by the sender
        if message.headers.get('disposition-notification-options') and \
                message.receiver and message.receiver.sign_key:
            self.digest_alg = \
                message.headers['disposition-notification-options'].split(
                    ';')[-1].split(',')[-1].strip().replace('-', '')
            signed_mdn = MIMEMultipart(
                'signed', protocol="application/pkcs7-signature")
            del signed_mdn['MIME-Version']
            signed_mdn.attach(self.payload)

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type('application/pkcs7-signature')
            signature.set_param('name', 'smime.p7s')
            signature.set_param('smime-type', 'signed-data')
            signature.add_header(
                'Content-Disposition', 'attachment', filename='smime.p7s')
            del signature['MIME-Version']
            signature.set_payload(sign_message(
                canonicalize(self.payload),
                self.digest_alg,
                message.receiver.sign_key
            ))
            encoders.encode_base64(signature)
            # logger.debug(
            #     'Signature for MDN created:\n%s' % signature.as_string())
            signed_mdn.set_param('micalg', self.digest_alg)
            signed_mdn.attach(signature)

            self.payload = signed_mdn

        # Update the headers of the final payload and set message boundary
        for k, v in mdn_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)
        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())

    def parse(self, raw_content, find_message_cb):
        """Function parses the RAW AS2 MDN, verifies it and extracts the
        processing status of the orginal AS2 message.

        :param raw_content:
            A byte string of the received HTTP headers followed by the body.

        :param find_message_cb:
            A callback the must returns the original Message Object. The
            original message-id and original recipient AS2 ID are passed
            as arguments to it.

        :returns:
            A two element tuple containing (status, detailed_status). The
            status is a string indicating the status of the transaction. The
            optional detailed_status gives additional information about the
            processing status.
        """

        status, detailed_status = None, None
        self.payload = parse_mime(raw_content)
        self.orig_message_id, orig_recipient = self.detect_mdn()

        # Call the find message callback which should return a Message instance
        orig_message = find_message_cb(self.orig_message_id, orig_recipient)

        # Extract the headers and save it
        mdn_headers = {}
        for k, v in self.payload.items():
            k = k.lower()
            if k == 'message-id':
                self.message_id = v.lstrip('<').rstrip('>')
            mdn_headers[k] = v

        if orig_message.receiver.mdn_digest_alg \
                and self.payload.get_content_type() != 'multipart/signed':
            status = 'failed/Failure'
            detailed_status = 'Expected signed MDN but unsigned MDN returned'
            return status, detailed_status

        if self.payload.get_content_type() == 'multipart/signed':
            signature = None
            message_boundary = (
                    '--' + self.payload.get_boundary()).encode('utf-8')
            for part in self.payload.walk():
                if part.get_content_type() == 'application/pkcs7-signature':
                    signature = part.get_payload(decode=True)
                elif part.get_content_type() == 'multipart/report':
                    self.payload = part

            # Verify the message, first using raw message and if it fails
            # then convert to canonical form and try again
            mic_content = extract_first_part(raw_content, message_boundary)
            verify_cert = orig_message.receiver.load_verify_cert()
            try:
                self.digest_alg = verify_message(
                    mic_content, signature, verify_cert)
            except IntegrityError:
                mic_content = canonicalize(self.payload)
                self.digest_alg = verify_message(
                    mic_content, signature, verify_cert)

        for part in self.payload.walk():
            if part.get_content_type() == 'message/disposition-notification':
                # logger.debug('Found MDN report for message %s:\n%s' % (
                #     orig_message.message_id, part.as_string()))

                mdn = part.get_payload()[-1]
                mdn_status = mdn['Disposition'].split(
                    ';').pop().strip().split(':')
                status = mdn_status[0]
                if status == 'processed':
                    mdn_mic = mdn.get('Received-Content-MIC', '').split(',')[0]

                    # TODO: Check MIC for all cases
                    if mdn_mic and orig_message.mic \
                            and mdn_mic != orig_message.mic.decode():
                        status = 'processed/warning'
                        detailed_status = 'Message Integrity check failed.'
                else:
                    detailed_status = ' '.join(mdn_status[1:]).strip()

        return status, detailed_status

    def detect_mdn(self):
        """ Function checks if the received raw message is an AS2 MDN or not.

        :raises MDNNotFound: If the received payload is not an MDN then this
        exception is raised.

        :return:
            A two element tuple containing (message_id, message_recipient). The
            message_id is the original AS2 message id and the message_recipient
            is the original AS2 message recipient.
        """
        mdn_message = None
        if self.payload.get_content_type() == 'multipart/report':
            mdn_message = self.payload
        elif self.payload.get_content_type() == 'multipart/signed':
            for part in self.payload.walk():
                if part.get_content_type() == 'multipart/report':
                    mdn_message = self.payload

        if not mdn_message:
            raise MDNNotFound('No MDN found in the received message')

        message_id, message_recipient = None, None
        for part in mdn_message.walk():
            if part.get_content_type() == 'message/disposition-notification':
                mdn = part.get_payload()[0]
                message_id = mdn.get('Original-Message-ID').strip('<>')
                message_recipient = mdn.get(
                    'Original-Recipient').split(';')[1].strip()
        return message_id, message_recipient
Пример #6
0
class Mdn:
    """Class for handling AS2 MDNs. Includes functions for both
    parsing and building them.
    """
    def __init__(self, mdn_mode=None, digest_alg=None, mdn_url=None):
        self.message_id = None
        self.orig_message_id = None
        self.payload = None
        self.mdn_mode = mdn_mode
        self.digest_alg = digest_alg
        self.mdn_url = mdn_url

    @property
    def content(self):
        """Function returns the body of the mdn message as a byte string"""

        if self.payload is not None:
            message_bytes = mime_to_bytes(self.payload)
            boundary = b"--" + self.payload.get_boundary().encode("utf-8")
            temp = message_bytes.split(boundary)
            temp.pop(0)
            return boundary + boundary.join(temp)
        return ""

    @property
    def headers(self):
        """Return the headers in the payload as a dictionary."""
        if self.payload:
            return dict(self.payload.items())
        return {}

    @property
    def headers_str(self):
        """Return the headers in the payload as a string."""
        message_header = ""
        if self.payload:
            for k, v in self.headers.items():
                message_header += f"{k}: {v}\r\n"
        return message_header.encode("utf-8")

    def build(
        self,
        message,
        status,
        detailed_status=None,
        confirmation_text=MDN_CONFIRM_TEXT,
        failed_text=MDN_FAILED_TEXT,
    ):
        """Function builds and signs an AS2 MDN message.

        :param message: The received AS2 message for which this is an MDN.

        :param status: The status of processing of the received AS2 message.

        :param detailed_status: The optional detailed status of processing of the received AS2
        message. Used to give additional error info (default "None")

        :param confirmation_text: The confirmation message sent in the first part of the MDN.

        :param failed_text: The failure message sent in the first part of the failed MDN.
        """

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip("<").rstrip(">")
        self.orig_message_id = message.message_id

        # Set up the message headers
        mdn_headers = {
            "AS2-Version": AS2_VERSION,
            "ediint-features": EDIINT_FEATURES,
            "Message-ID": f"<{self.message_id}>",
            "AS2-From": quote_as2name(message.headers.get("as2-to")),
            "AS2-To": quote_as2name(message.headers.get("as2-from")),
            "Date": email_utils.formatdate(localtime=True),
            "user-agent": "pyAS2 Open Source AS2 Software",
        }

        # Set the confirmation text message here
        # overwrite with organization specific message
        if message.receiver and message.receiver.mdn_confirm_text:
            confirmation_text = message.receiver.mdn_confirm_text

        # overwrite with partner specific message
        if message.sender and message.sender.mdn_confirm_text:
            confirmation_text = message.sender.mdn_confirm_text

        if status != "processed":
            confirmation_text = failed_text

        self.payload = MIMEMultipart("report",
                                     report_type="disposition-notification")

        # Create and attach the MDN Text Message
        mdn_text = email_message.Message()
        mdn_text.set_payload(f"{confirmation_text}\r\n")
        mdn_text.set_type("text/plain")
        del mdn_text["MIME-Version"]
        encoders.encode_7or8bit(mdn_text)
        self.payload.attach(mdn_text)

        # Create and attache the MDN Report Message
        mdn_base = email_message.Message()
        mdn_base.set_type("message/disposition-notification")
        mdn_report = "Reporting-UA: pyAS2 Open Source AS2 Software\r\n"
        mdn_report += f'Original-Recipient: rfc822; {message.headers.get("as2-to")}\r\n'
        mdn_report += f'Final-Recipient: rfc822; {message.headers.get("as2-to")}\r\n'
        mdn_report += f"Original-Message-ID: <{message.message_id}>\r\n"
        mdn_report += f"Disposition: automatic-action/MDN-sent-automatically; {status}"
        if detailed_status:
            mdn_report += f": {detailed_status}"
        mdn_report += "\r\n"
        if message.mic:
            mdn_report += f"Received-content-MIC: {message.mic.decode()}, {message.digest_alg}\r\n"
        mdn_base.set_payload(mdn_report)
        del mdn_base["MIME-Version"]
        encoders.encode_7or8bit(mdn_base)
        self.payload.attach(mdn_base)

        logger.debug(
            f"MDN report for message {message.message_id} created:\n{mime_to_bytes(mdn_base)}"
        )

        # Sign the MDN if it is requested by the sender
        if (message.headers.get("disposition-notification-options")
                and message.receiver and message.receiver.sign_key):
            self.digest_alg = (
                message.headers["disposition-notification-options"].split(
                    ";")[-1].split(",")[-1].strip().replace("-", ""))
            signed_mdn = MIMEMultipart("signed",
                                       protocol="application/pkcs7-signature")
            del signed_mdn["MIME-Version"]
            signed_mdn.attach(self.payload)

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type("application/pkcs7-signature")
            signature.set_param("name", "smime.p7s")
            signature.set_param("smime-type", "signed-data")
            signature.add_header("Content-Disposition",
                                 "attachment",
                                 filename="smime.p7s")
            del signature["MIME-Version"]

            signed_data = sign_message(canonicalize(self.payload),
                                       self.digest_alg,
                                       message.receiver.sign_key)
            signature.set_payload(signed_data)
            encoders.encode_base64(signature)

            signed_mdn.set_param("micalg", self.digest_alg)
            signed_mdn.attach(signature)

            self.payload = signed_mdn
            logger.debug(f"Signing the MDN for message {message.message_id}")

        # Update the headers of the final payload and set message boundary
        for k, v in mdn_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)
        self.payload.set_boundary(make_mime_boundary())
        logger.debug(f"MDN generated for message {message.message_id} with "
                     f"content:\n {mime_to_bytes(self.payload)}")

    def parse(self, raw_content, find_message_cb):
        """Function parses the RAW AS2 MDN, verifies it and extracts the
        processing status of the orginal AS2 message.

        :param raw_content:
            A byte string of the received HTTP headers followed by the body.

        :param find_message_cb:
            A callback the must returns the original Message Object. The
            original message-id and original recipient AS2 ID are passed
            as arguments to it.

        :returns:
            A two element tuple containing (status, detailed_status). The
            status is a string indicating the status of the transaction. The
            optional detailed_status gives additional information about the
            processing status.
        """

        status, detailed_status = None, None
        try:
            self.payload = parse_mime(raw_content)
            self.orig_message_id, orig_recipient = self.detect_mdn()

            # Call the find message callback which should return a Message instance
            orig_message = find_message_cb(self.orig_message_id,
                                           orig_recipient)

            # Extract the headers and save it
            mdn_headers = {}
            for k, v in self.payload.items():
                k = k.lower()
                if k == "message-id":
                    self.message_id = v.lstrip("<").rstrip(">")
                mdn_headers[k] = v

            if (orig_message.receiver.mdn_digest_alg
                    and self.payload.get_content_type() != "multipart/signed"):
                status = "failed/Failure"
                detailed_status = "Expected signed MDN but unsigned MDN returned"
                return status, detailed_status

            if self.payload.get_content_type() == "multipart/signed":
                logger.debug(
                    f"Verifying signed MDN: \n{mime_to_bytes(self.payload)}")
                message_boundary = (
                    "--" + self.payload.get_boundary()).encode("utf-8")

                # Extract the signature and the signed payload
                signature = None
                signature_types = [
                    "application/pkcs7-signature",
                    "application/x-pkcs7-signature",
                ]
                for part in self.payload.walk():
                    if part.get_content_type() in signature_types:
                        signature = part.get_payload(decode=True)
                    elif part.get_content_type() == "multipart/report":
                        self.payload = part

                # Verify the message, first using raw message and if it fails
                # then convert to canonical form and try again
                mic_content = extract_first_part(raw_content, message_boundary)
                verify_cert = orig_message.receiver.load_verify_cert()
                try:
                    self.digest_alg = verify_message(mic_content, signature,
                                                     verify_cert)
                except IntegrityError:
                    mic_content = canonicalize(self.payload)
                    self.digest_alg = verify_message(mic_content, signature,
                                                     verify_cert)

            for part in self.payload.walk():
                if part.get_content_type(
                ) == "message/disposition-notification":
                    logger.debug(
                        f"MDN report for message {orig_message.message_id}:\n{part.as_string()}"
                    )

                    mdn = part.get_payload()[-1]
                    mdn_status = mdn["Disposition"].split(
                        ";").pop().strip().split(":")
                    status = mdn_status[0]
                    if status == "processed":
                        # Compare the original mic with the received mic
                        mdn_mic = mdn.get("Received-Content-MIC",
                                          "").split(",")[0]
                        if (mdn_mic and orig_message.mic
                                and mdn_mic != orig_message.mic.decode()):
                            status = "processed/warning"
                            detailed_status = "Message Integrity check failed."
                    else:
                        detailed_status = " ".join(mdn_status[1:]).strip()
        except MDNNotFound:
            status = "failed/Failure"
            detailed_status = "mdn-not-found"
        except Exception as e:  # pylint: disable=W0703
            status = "failed/Failure"
            detailed_status = f"Failed to parse received MDN. {e}"
            logger.error(
                f"Failed to parse AS2 MDN\n: {traceback.format_exc()}")
        return status, detailed_status

    def detect_mdn(self):
        """Function checks if the received raw message is an AS2 MDN or not.

        :raises MDNNotFound: If the received payload is not an MDN then this
        exception is raised.

        :return:
            A two element tuple containing (message_id, message_recipient). The
            message_id is the original AS2 message id and the message_recipient
            is the original AS2 message recipient.
        """
        mdn_message = None
        if self.payload.get_content_type() == "multipart/report":
            mdn_message = self.payload
        elif self.payload.get_content_type() == "multipart/signed":
            for part in self.payload.walk():
                if part.get_content_type() == "multipart/report":
                    mdn_message = self.payload

        if not mdn_message:
            raise MDNNotFound("No MDN found in the received message")

        message_id, message_recipient = None, None
        for part in mdn_message.walk():
            if part.get_content_type() == "message/disposition-notification":
                mdn = part.get_payload()[0]
                message_id = mdn.get("Original-Message-ID").strip("<>")
                message_recipient = None
                if "Original-Recipient" in mdn:
                    message_recipient = mdn["Original-Recipient"].split(
                        ";")[1].strip()
                elif "Final-Recipient" in mdn:
                    message_recipient = mdn["Final-Recipient"].split(
                        ";")[1].strip()
        return message_id, message_recipient
Пример #7
0
html_msg = MIMEText('<strong>hello!</strong>', 'html')
with open("s/python_logo.png","rb") as fb:
    image_msg = MIMEImage(fb.read())


msg.attach(text_msg)
msg.attach(html_msg)

# walk() method which allows you to move through all of the messages parts
# and subparts.
'''

srv = smtplib.SMTP("smtp.gmail.com", 587)
srv.ehlo()
srv.starttls()
srv.login("*****@*****.**", "23688mfb999705949")
srv.sendmail(msg["From"], msg["To"],msg.as_string())
srv.quit()

'''
print(msg.as_string())
print("*" * 30, "\n")
print(text_msg.as_string())
print("*" * 30, "\n")
print(html_msg.as_string())

print("*" * 30, "\n")
print(msg.get_content_type())
print(text_msg.get_content_type())
print(html_msg.get_content_type())
Пример #8
0
text_msg = MIMEText('hello!', 'plain')
html_msg = MIMEText('<strong>hello!</strong>', 'html')
with open("s/python_logo.png", "rb") as fb:
    image_msg = MIMEImage(fb.read())

msg.attach(text_msg)
msg.attach(html_msg)

# walk() method which allows you to move through all of the messages parts
# and subparts.
'''

srv = smtplib.SMTP("smtp.gmail.com", 587)
srv.ehlo()
srv.starttls()
srv.login("*****@*****.**", "23688mfb999705949")
srv.sendmail(msg["From"], msg["To"],msg.as_string())
srv.quit()

'''
print(msg.as_string())
print("*" * 30, "\n")
print(text_msg.as_string())
print("*" * 30, "\n")
print(html_msg.as_string())

print("*" * 30, "\n")
print(msg.get_content_type())
print(text_msg.get_content_type())
print(html_msg.get_content_type())