Ejemplo n.º 1
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = "multipart/signed"
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = "application/x-signature"
    SIGNATURE_DESC = "Abstract Digital Signature"

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split("/"))
        self.sigblock.set_param("name", "signature.asc")
        for h, v in (
            ("Content-Description", self.SIGNATURE_DESC),
            ("Content-Disposition", 'attachment; filename="signature.asc"'),
        ):
            self.sigblock.add_header(h, v)

    def wrap(self, msg):
        MimeWrapper.wrap(self, msg)
        self.attach(msg)
        self.attach(self.sigblock)

        message_text = Normalize(self.flatten(msg))
        from_key = self.get_keys([self.sender])[0]
        status, sig = self.crypto.sign(message_text, fromkey=from_key, armor=True)
        if status == 0:
            self.sigblock.set_payload(sig)
            return self.container
        else:
            raise SignatureFailureError(_("Failed to sign message!"))
Ejemplo n.º 2
0
    def build_email(self, email_to, subject, body, attachments=None, subtype='plain'):
        """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.

           :param list email_to: list of recipient addresses (to be joined with commas)
           :param string subject: email subject (no pre-encoding/quoting necessary)
           :param string body: email body, of the type ``subtype`` (by default, plaintext).
                               If html subtype is used, the message will be automatically converted
                               to plaintext and wrapped in multipart/alternative, unless an explicit
                               ``body_alternative`` version is passed.
           :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                  must match the format of the ``body`` parameter. Default is 'plain',
                                  making the content part of the mail "text/plain".
           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
                                    containing the bytes of the attachment

           :return: the new RFC2822 email message
        """

        msg = MIMEMultipart()
        msg['Subject'] = subject
        msg['To'] = email_to
        msg['Date'] = formatdate()

        email_text_part = MIMEText(body or u'', _subtype=subtype, _charset='utf-8')
        msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent) in attachments:
                part = MIMEBase('application', "octet-stream")
                part.set_param('name', fname)
                part.add_header('Content-Disposition', 'attachment', filename=fname)
                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 3
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = 'multipart/signed'
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = 'application/x-signature'
    SIGNATURE_DESC = 'Abstract Digital Signature'

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split('/'))
        self.sigblock.set_param("name", "signature.asc")
        for h, v in (("Content-Description", self.SIGNATURE_DESC),
                     ("Content-Disposition",
                      "attachment; filename=\"signature.asc\"")):
            self.sigblock.add_header(h, v)

    def wrap(self, msg):
        MimeWrapper.wrap(self, msg)
        self.attach(msg)
        self.attach(self.sigblock)

        message_text = Normalize(self.flatten(msg))
        from_key = self.get_keys([self.sender])[0]
        status, sig = self.crypto.sign(message_text,
                                       fromkey=from_key, armor=True)
        if status == 0:
            self.sigblock.set_payload(sig)
            return self.container
        else:
            raise SignatureFailureError(_('Failed to sign message!'))
Ejemplo n.º 4
0
    def build_email(self,
                    email_from,
                    email_to,
                    subject,
                    body,
                    email_cc=None,
                    email_bcc=None,
                    subtype='pain',
                    headers=None,
                    attachments=None):
        '''
        :param string subtype:  optional mime subtype for the text body (usually 'plain' or 'html'),
                                must match the format of the ``body`` parameter. Default is 'plain',
                                making the content part of the mail "text/plain".
        '''
        assert email_from, "You must provide a sender address."

        headers = headers or {}
        if not email_cc: email_cc = []
        if not email_bcc: email_bcc = []
        if not body: body = u''

        email_text_part = MIMEText(body.encode('utf_8'),
                                   _subtype=subtype,
                                   _charset='utf_8')
        msg = MIMEMultipart()

        msg['Subject'] = subject.encode('utf_8')
        msg['From'] = email_from.encode('utf_8')
        msg['To'] = email_to.encode('utf_8')

        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.items():
            msg[key.encode('utf_8')] = value.encode('uft_8')

        msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent) in attachments:
                filename_rfc2047 = fname.encode('utf_8')
                part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition',
                                'attachment',
                                filename=filename_rfc2047)

                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 5
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = 'multipart/signed'
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = 'application/x-signature'
    SIGNATURE_DESC = 'Abstract Digital Signature'

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split('/'))
        self.sigblock.set_param("name", "signature.asc")
        for h, v in (("Content-Description", self.SIGNATURE_DESC),
                     ("Content-Disposition",
                      "attachment; filename=\"signature.asc\"")):
            self.sigblock.add_header(h, v)

    def _update_crypto_status(self, part):
        part.signature_info.part_status = 'verified'

    def wrap(self, msg, prefer_inline=False):
        from_key = self.get_keys([self.sender])[0]

        if prefer_inline:
            prefer_inline = self.get_only_text_part(msg)

        if prefer_inline is not False:
            message_text = Normalize(
                prefer_inline.get_payload(None, True).strip() + '\r\n\r\n')
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             clearsign=True,
                                             armor=True)
            if status == 0:
                _update_text_payload(prefer_inline, sig)
                self._update_crypto_status(prefer_inline)
                return msg

        else:
            MimeWrapper.wrap(self, msg)
            self.attach(msg)
            self.attach(self.sigblock)
            message_text = self.flatten(msg)
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             armor=True)
            if status == 0:
                self.sigblock.set_payload(sig)
                self._update_crypto_status(self.container)
                return self.container

        raise SignatureFailureError(_('Failed to sign message!'), from_key)
Ejemplo n.º 6
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = 'multipart/signed'
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = 'application/x-signature'
    SIGNATURE_DESC = 'Abstract Digital Signature'

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split('/'))
        self.sigblock.set_param("name", "signature.asc")
        for h, v in (("Content-Description", self.SIGNATURE_DESC),
                     ("Content-Disposition",
                      "attachment; filename=\"signature.asc\"")):
            self.sigblock.add_header(h, v)

    def _update_crypto_status(self, part):
        part.signature_info.part_status = 'verified'

    def wrap(self, msg, prefer_inline=False):
        from_key = self.get_keys([self.sender])[0]

        if prefer_inline:
            prefer_inline = self.get_only_text_part(msg)

        if prefer_inline is not False:
            message_text = Normalize(prefer_inline.get_payload(None, True)
                                     .strip() + '\r\n\r\n')
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             clearsign=True,
                                             armor=True)
            if status == 0:
                _update_text_payload(prefer_inline, sig)
                self._update_crypto_status(prefer_inline)
                return msg

        else:
            MimeWrapper.wrap(self, msg)
            self.attach(msg)
            self.attach(self.sigblock)
            message_text = self.flatten(msg)
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key, armor=True)
            if status == 0:
                self.sigblock.set_payload(sig)
                self._update_crypto_status(self.container)
                return self.container

        raise SignatureFailureError(_('Failed to sign message!'), from_key)
Ejemplo n.º 7
0
    def _send_message(self, subject, message, attach_name, attach_data):
        """
        メール送信
        """

        msg = MIMEMultipart()

        # ヘッダ
        msg['Subject'] = subject
        msg['From']    = self.email_attr['from']
        msg['To']      = self.email_attr['reply_to']
        msg['Bcc']     = self.email_attr['from']

        # 本文
        body = MIMEText(message, 'plain', self.encode)
        msg.attach(body)

        # 添付ファイル
        if attach_data:
            file_name = "%s.zip" % attach_name
            attachment = MIMEBase('application', 'zip')
            attachment.set_param('name', file_name)
            attachment.set_payload(attach_data)
            encoders.encode_base64(attachment)
            attachment.add_header("Content-Dispositon", "attachment", filename=file_name)
            msg.attach(attachment)

        # 送信
        smtp_server   = self._get_decrypted_environ("SMTP_SERVER")
        smtp_port     = self._get_decrypted_environ("SMTP_PORT")
        smtp_user     = self._get_decrypted_environ("SMTP_USER")
        smtp_password = self._get_decrypted_environ("SMTP_PASSWORD")
        smtp = smtplib.SMTP(smtp_server, smtp_port)
        smtp.ehlo()
        smtp.starttls()
        smtp.ehlo()
        smtp.login(smtp_user, smtp_password)
        smtp.send_message(msg)
        smtp.quit()
        print("Successfully sent email")

        return
Ejemplo n.º 8
0
    def build_email(self,email_from,email_to,subject,body,email_cc=None, email_bcc=None,subtype='pain',headers = None,attachments=None):
        '''
        :param string subtype:  optional mime subtype for the text body (usually 'plain' or 'html'),
                                must match the format of the ``body`` parameter. Default is 'plain',
                                making the content part of the mail "text/plain".
        '''
        assert email_from, "You must provide a sender address."
        
        headers = headers or {}
        if not email_cc: email_cc = []
        if not email_bcc: email_bcc = []
        if not body: body = u''
        
        email_text_part = MIMEText(body.encode('utf_8'), _subtype=subtype, _charset='utf_8')
        msg = MIMEMultipart()
        
        msg['Subject'] = subject.encode('utf_8')
        msg['From'] = email_from.encode('utf_8')
        msg['To'] = email_to.encode('utf_8')
        
        
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.items():
            msg[key.encode('utf_8')] = value.encode('uft_8')
            
        msg.attach(email_text_part)
        
        if attachments:
            for (fname, fcontent) in attachments:
                filename_rfc2047 = fname.encode('utf_8')
                part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)

                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 9
0
    def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
                    attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
                    body_alternative=None, subtype_alternative='plain'):
        """ copy-pasted from openerp/addons/base/models/ir_mail_server.py::build_email """

        ftemplate = '__image-%s__'
        fcounter = 0
        attachments = attachments or []

        pattern = re.compile(r'"data:image/png;base64,[^"]*"')
        pos = 0
        new_body = ''
        body = body or ''
        while True:
            match = pattern.search(body, pos)
            if not match:
                break
            s = match.start()
            e = match.end()
            data = body[s + len('"data:image/png;base64,'):e - 1]
            new_body += body[pos:s]

            fname = ftemplate % fcounter
            fcounter += 1
            attachments.append((fname, base64.b64decode(data)))

            new_body += '"cid:%s"' % fname
            pos = e

        new_body += body[pos:]
        body = new_body

        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {}  # need valid dict later

        if not email_cc:
            email_cc = []
        if not email_bcc:
            email_bcc = []
        if not body:
            body = u''

        email_body_utf8 = ustr(body).encode('utf-8')
        email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.iteritems():
            msg[ustr(key).encode('utf-8')] = encode_header(value)

        if subtype == 'html' and not body_alternative and html2text:
            # Always provide alternative text body ourselves if possible.
            text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_utf8 = ustr(body_alternative).encode('utf-8')
            alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)
                part.add_header('Content-ID', '<%s>' % filename_rfc2047)  # NEW STUFF

                part.set_payload(fcontent)
                Encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 10
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = 'multipart/signed'
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = 'application/x-signature'
    SIGNATURE_DESC = 'Abstract Digital Signature'

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        name = 'signature.html' if self.use_html_wrapper else 'signature.asc'
        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split('/'))
        self.sigblock.set_param("name", name)
        for h, v in (("Content-Description", self.SIGNATURE_DESC),
                     ("Content-Disposition",
                      "attachment; filename=\"%s\"" % name)):
            self.sigblock.add_header(h, v)

    def _wrap_sig_in_html(self, sig):
        return ("<html><body><h1>%(title)s</h1><p>\n\n%(description)s\n\n</p>"
                "<pre>\n%(sig)s\n</pre><hr>"
                "<i><a href='%(ad_url)s'>%(ad)s</a>.</i></body></html>"
                ) % self._wrap_sig_in_html_vars(sig)

    def _wrap_sig_in_html_vars(self, sig):
        return {
            # FIXME: We deliberately do not flag these messages for i18n
            #        translation, since we rely on 7-bit content here so as
            #        not to complicate the MIME structure of the message.
            "title":
            "Digital Signature",
            "description":
            ("This is a digital signature, which can be used to verify\n"
             "the authenticity of this message. You can safely discard\n"
             "or ignore this file if your e-mail software does not\n"
             "support digital signatures."),
            "ad":
            "Generated by Mailpile",
            "ad_url":
            "https://www.mailpile.is/",  # FIXME: Link to help?
            "sig":
            sig
        }

    def _update_crypto_status(self, part):
        part.signature_info.part_status = 'verified'

    def wrap(self, msg, prefer_inline=False):
        from_key = self.get_keys([self.sender])[0]

        if prefer_inline:
            prefer_inline = self.get_only_text_part(msg)
        else:
            prefer_inline = False

        if prefer_inline is not False:
            message_text = Normalize(
                prefer_inline.get_payload(None, True).strip() + '\r\n\r\n')
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             clearsign=True,
                                             armor=True)
            if status == 0:
                _update_text_payload(prefer_inline, sig)
                self._update_crypto_status(prefer_inline)
                return msg

        else:
            msg = self.prepare_wrap(msg)
            self.attach(msg)
            self.attach(self.sigblock)
            message_text = self.flatten(msg)
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             armor=True)
            if status == 0:
                if self.use_html_wrapper:
                    sig = self._wrap_sig_in_html(sig)
                self.sigblock.set_payload(sig)
                self._update_crypto_status(self.container)
                return self.container

        raise SignatureFailureError(_('Failed to sign message!'), from_key)
Ejemplo n.º 11
0
    def build_email(self,
                    email_from,
                    email_to,
                    subject,
                    body,
                    email_cc=None,
                    email_bcc=None,
                    reply_to=False,
                    attachments=None,
                    message_id=None,
                    references=None,
                    object_id=False,
                    subtype='plain',
                    headers=None,
                    body_alternative=None,
                    subtype_alternative='plain'):
        """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.

           :param string email_from: sender email address
           :param list email_to: list of recipient addresses (to be joined with commas) 
           :param string subject: email subject (no pre-encoding/quoting necessary)
           :param string body: email body, of the type ``subtype`` (by default, plaintext).
                               If html subtype is used, the message will be automatically converted
                               to plaintext and wrapped in multipart/alternative, unless an explicit
                               ``body_alternative`` version is passed.
           :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
           :param string reply_to: optional value of Reply-To header
           :param string object_id: optional tracking identifier, to be included in the message-id for
                                    recognizing replies. Suggested format for object-id is "res_id-model",
                                    e.g. "12345-crm.lead".
           :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                  must match the format of the ``body`` parameter. Default is 'plain',
                                  making the content part of the mail "text/plain".
           :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
                                              or 'html'). Default is 'plain'.
           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
                                    containing the bytes of the attachment
           :param list email_cc: optional list of string values for CC header (to be joined with commas)
           :param list email_bcc: optional list of string values for BCC header (to be joined with commas)
           :param dict headers: optional map of headers to set on the outgoing mail (may override the
                                other headers, including Subject, Reply-To, Message-Id, etc.)
           :rtype: email.message.Message (usually MIMEMultipart)
           :return: the new RFC2822 email message
        """
        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {}  # need valid dict later
        email_cc = email_cc or []
        email_bcc = email_bcc or []
        body = body or u''

        email_body = ustr(body)
        email_text_part = MIMEText(email_body,
                                   _subtype=subtype,
                                   _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(
                COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(
                COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.items():
            msg[pycompat.to_native(ustr(key))] = encode_header(value)

        if subtype == 'html' and not body_alternative:
            # Always provide alternative text body ourselves if possible.
            text = html2text.html2text(email_body)
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(
                MIMEText(text, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_ = ustr(body_alternative)
            alternative_body_part = MIMEText(body_alternative_,
                                             _subtype=subtype_alternative,
                                             _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent, mime) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                if mime and '/' in mime:
                    maintype, subtype = mime.split('/', 1)
                    part = MIMEBase(maintype, subtype)
                else:
                    part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition',
                                'attachment',
                                filename=filename_rfc2047)

                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
    def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
               attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
               body_alternative=None, subtype_alternative='plain'):
        """ copy-pasted from openerp/addons/base/ir/ir_mail_server.py::build_email """

        ftemplate = '__image-%s__'
        fcounter = 0
        attachments = attachments or []

        pattern = re.compile(r'"data:image/png;base64,[^"]*"')
        pos = 0
        new_body = ''
        while True:
            match = pattern.search(body, pos)
            if not match:
                break
            s = match.start()
            e = match.end()
            data = body[s+len('"data:image/png;base64,'):e-1]
            new_body += body[pos:s]

            fname = ftemplate % fcounter
            fcounter += 1
            attachments.append( (fname, base64.b64decode(data)) )

            new_body += '"cid:%s"' % fname
            pos = e

        new_body += body[pos:]
        body = new_body



        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {} # need valid dict later

        if not email_cc: email_cc = []
        if not email_bcc: email_bcc = []
        if not body: body = u''

        email_body_utf8 = ustr(body).encode('utf-8')
        email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.iteritems():
            msg[ustr(key).encode('utf-8')] = encode_header(value)

        if subtype == 'html' and not body_alternative and html2text:
            # Always provide alternative text body ourselves if possible.
            text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_utf8 = ustr(body_alternative).encode('utf-8')
            alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)
                part.add_header('Content-ID', '<%s>' % filename_rfc2047) # NEW STUFF

                part.set_payload(fcontent)
                Encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 13
0
    def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
               attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
               body_alternative=None, subtype_alternative='plain'):
        """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.

           :param string email_from: sender email address
           :param list email_to: list of recipient addresses (to be joined with commas) 
           :param string subject: email subject (no pre-encoding/quoting necessary)
           :param string body: email body, of the type ``subtype`` (by default, plaintext).
                               If html subtype is used, the message will be automatically converted
                               to plaintext and wrapped in multipart/alternative, unless an explicit
                               ``body_alternative`` version is passed.
           :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
           :param string reply_to: optional value of Reply-To header
           :param string object_id: optional tracking identifier, to be included in the message-id for
                                    recognizing replies. Suggested format for object-id is "res_id-model",
                                    e.g. "12345-crm.lead".
           :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                  must match the format of the ``body`` parameter. Default is 'plain',
                                  making the content part of the mail "text/plain".
           :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
                                              or 'html'). Default is 'plain'.
           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
                                    containing the bytes of the attachment
           :param list email_cc: optional list of string values for CC header (to be joined with commas)
           :param list email_bcc: optional list of string values for BCC header (to be joined with commas)
           :param dict headers: optional map of headers to set on the outgoing mail (may override the
                                other headers, including Subject, Reply-To, Message-Id, etc.)
           :rtype: email.message.Message (usually MIMEMultipart)
           :return: the new RFC2822 email message
        """
        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {} # need valid dict later

        if not email_cc: email_cc = []
        if not email_bcc: email_bcc = []
        if not body: body = u''

        email_body_utf8 = ustr(body).encode('utf-8')
        email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.iteritems():
            msg[ustr(key).encode('utf-8')] = encode_header(value)

        if subtype == 'html' and not body_alternative and html2text:
            # Always provide alternative text body ourselves if possible.
            text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_utf8 = ustr(body_alternative).encode('utf-8')
            alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)

                part.set_payload(fcontent)
                Encoders.encode_base64(part)
                msg.attach(part)
        return msg
Ejemplo n.º 14
0
def sendArf(item, spam=False):
    global reportSender
    global mailSmtp
    global reportEmailCc
    global reportEmailSpamCc

    msg = MIMEBase('multipart', 'report')
    msg.set_param('report-type', 'feedback-report', requote=False)

    msg["To"] = str(item['emailAbuse'])
    msg["From"] = reportSender
    msg["Subject"] = "Abuse report for: " + str(item['subject'])

    if spam:
        text = "This is an email in the abuse report format (ARF) for an email message coming via these \r\n"
        text = text + "IPs " + str(item['sourceIp']) + " on " + str(
            item['arrivalDate']) + ".\r\n"
        text = text + "This report indicates that the attached email was not wanted by the recipient.\r\n"
        text = text + "This report may indicates a compromised machine and may contain URLs to malware, treat with caution!\r\n\r\n"
        text = text + "This ARF report contains all the information you will need to assess the problem.\r\n"
        text = text + "The zip attachment is the complete email encrypted with the password " + str(
            arfPassword) + "\r\n"
        text = text + "For more information about this format please see http://tools.ietf.org/html/rfc5965.\r\n"
    else:
        text = "This is an email in the abuse report format (ARF) for an email message received from \r\n"
        text = text + "IP " + str(item['sourceIp']) + " " + str(
            item['sourceDomain']) + " on " + str(
                item['arrivalDate']) + " UTC.\r\n"
        text = text + "This report likely indicates a compromised machine and may contain URLs to malware, treat with caution!\r\n\r\n"
        text = text + "The attached email was selected amongst emails that failed DMARC,\r\n"
        text = text + "therefore it indicates that the author tried to pass for someone else\r\n"
        text = text + "indicating fraud and not spam. The faster you fix or isolate the compromised machine, \r\n"
        text = text + "the better you protect your customers or members and the Internet at large.\r\n\r\n"
        text = text + "This ARF report contains all the information you will need to assess the problem.\r\n"
        text = text + "The zip attachment is the complete email encrypted with the password " + str(
            arfPassword) + "\r\n"
        text = text + "For more information about this format please see http://tools.ietf.org/html/rfc5965.\r\n"

    msgtxt = MIMEText(text)
    msg.attach(msgtxt)

    msgreport = MIMEBase('message', "feedback-report")
    msgreport.set_charset("US-ASCII")

    if spam:
        text = "Feedback-Type: abuse\r\n"
    else:
        text = "Feedback-Type: fraud\r\n"
    text = text + "User-Agent: pyforensic/1.1\r\n"
    text = text + "Version: 1.0\r\n"
    if not spam:
        text = text + "Source-IP: " + str(item['sourceIp']) + "\r\n"
    else:
        ipList = item['sourceIp'].split(", ")
        for ip in ipList:
            text = text + "Source-IP: " + str(ip) + "\r\n"

    text = text + "Arrival-Date: " + str(item['arrivalDate']) + " UTC\r\n"

    text = text + "Attachment-Password: "******"\r\n"

    if 'urlList' in item:
        for uri in item['urlList']:
            o = urlparse.urlparse(uri)
            urlReport = True
            if o.hostname is not None:
                for domain in wldomain:
                    if o.hostname[-len(domain):] == domain:
                        urlReport = False
                if urlReport == True:
                    text = text + "Reported-Uri: " + str(uri) + "\r\n"

    msgreport.set_payload(text)
    msg.attach(msgreport)

    #msgrfc822 = MIMEBase('message', "rfc822")
    msgrfc822 = MIMEBase('text', "rfc822-headers")
    msgrfc822.add_header('Content-Disposition', 'inline')
    parts = re.split(r'\r\n\r\n|\n\n', item['content'])
    rfc822headers = parts[0]
    #msgrfc822.set_payload(item['content'])
    msgrfc822.set_payload(rfc822headers)

    msg.attach(msgrfc822)

    #prepare the zip encrypted
    temp = tempfile.NamedTemporaryFile(prefix='mail',
                                       suffix='.eml',
                                       delete=False)
    tempname = temp.name
    temp.write(item['content'])
    temp.flush()
    ziptemp = tempfile.NamedTemporaryFile(prefix='mail',
                                          suffix='.zip',
                                          delete=True)
    ziptempname = ziptemp.name
    ziptemp.close()
    workdir = os.path.dirname(ziptempname)
    filenamezip = os.path.basename(ziptempname)
    filenameemail = os.path.basename(tempname)
    os.chdir(workdir)
    option = '-P%s' % arfPassword
    rc = subprocess.call(['zip', option] + [filenamezip, filenameemail])
    temp.close()

    ziptemp = open(ziptempname, "r")
    msgzip = MIMEBase('application', "zip")
    msgzip.set_payload(ziptemp.read())
    encoders.encode_base64(msgzip)
    msgzip.add_header('Content-Disposition',
                      'attachment',
                      filename=filenamezip)
    ziptemp.close()

    msg.attach(msgzip)

    #delete created files
    os.remove(ziptempname)
    os.remove(tempname)

    #print "******************\r\n"
    #print msg.as_string()
    #print "******************\r\n"

    s = smtplib.SMTP(mailSmtp)
    # send to IP owners first
    if msg["To"] != "":
        toList = msg["To"].split(",")
        s.sendmail(msg["From"], toList, msg.as_string())
    # send a copy
    reportEmail = reportEmailCc
    if spam:
        reportEmail = reportEmailSpamCc
    if reportEmail != "":
        toList = reportEmail.split(",")
        for emailAddress in toList:
            if msg.has_key("To"):
                msg.replace_header("To", str(emailAddress))
            else:
                msg["To"] = str(emailAddress)
            s.sendmail(msg["From"], emailAddress, msg.as_string())

    s.quit()
Ejemplo n.º 15
0
        field = field[0]
    if hasattr(field, "file") and field.file:
        payload = field.file.read()
        if len(payload) == 0:
            continue
        type_ = field.type
        ce = None
        if (not type_) or type_ == 'application/octet-stream':
            type_, ce = mimetypes.guess_type(field.filename)
        if not type_:
            type_ = 'application/octet-stream'
        m = MIMEBase(*tuple(type_.split('/')))
        if ce:
            m['Content-Encoding'] = ce
        fname = key + '_' + clean_fname(field.filename or 'NONE')
        m.set_param('name', fname)
        m['Content-Disposition'] = 'attachment'
        m.set_param('filename', fname, 'Content-Disposition')
        m.set_payload(payload)
        encoders.encode_base64(m)
        parts.attach(m)


class MyForm(dict):
    def __init__(self, form):
        self.form = form

    def __getitem__(self, key):
        s = self.form.getfirst(key)
        if s is None: return ""
        assert len(s) < 1024
Ejemplo n.º 16
0
def sendArf(item, spam=False):
    global reportSender
    global mailSmtp
    global reportEmailCc
    global reportEmailSpamCc

    msg = MIMEBase('multipart','report')
    msg.set_param('report-type','feedback-report',requote=False)

    msg["To"] = str(item['emailAbuse'])
    msg["From"] = reportSender
    msg["Subject"] = "Abuse report for: "+str(item['subject'])

    if spam:
        text = "This is an email in the abuse report format (ARF) for an email message coming via these \r\n"
        text = text+"IPs "+str(item['sourceIp'])+" on "+str(item['arrivalDate'])+".\r\n"
        text = text+"This report indicates that the attached email was not wanted by the recipient.\r\n"
        text = text+"This report may indicates a compromised machine and may contain URLs to malware, treat with caution!\r\n\r\n"
        text = text+"This ARF report contains all the information you will need to assess the problem.\r\n"
        text = text+"The zip attachment is the complete email encrypted with the password "+str(arfPassword)+"\r\n";
        text = text+"For more information about this format please see http://tools.ietf.org/html/rfc5965.\r\n";
    else:
        text = "This is an email in the abuse report format (ARF) for an email message received from \r\n"
        text = text+"IP "+str(item['sourceIp'])+" "+str(item['sourceDomain'])+" on "+str(item['arrivalDate'])+" UTC.\r\n"
        text = text+"This report likely indicates a compromised machine and may contain URLs to malware, treat with caution!\r\n\r\n"
        text = text+"The attached email was selected amongst emails that failed DMARC,\r\n"
        text = text+"therefore it indicates that the author tried to pass for someone else\r\n"
        text = text+"indicating fraud and not spam. The faster you fix or isolate the compromised machine, \r\n"
        text = text+"the better you protect your customers or members and the Internet at large.\r\n\r\n"
        text = text+"This ARF report contains all the information you will need to assess the problem.\r\n"
        text = text+"The zip attachment is the complete email encrypted with the password "+str(arfPassword)+"\r\n";
        text = text+"For more information about this format please see http://tools.ietf.org/html/rfc5965.\r\n";

    msgtxt = MIMEText(text)
    msg.attach(msgtxt)

    msgreport = MIMEBase('message', "feedback-report")
    msgreport.set_charset("US-ASCII")
    
    if spam:
        text = "Feedback-Type: abuse\r\n"
    else:
        text = "Feedback-Type: fraud\r\n"
    text = text + "User-Agent: pyforensic/1.1\r\n"
    text = text + "Version: 1.0\r\n"
    if not spam:
        text = text + "Source-IP: "+str(item['sourceIp'])+"\r\n"
    else:
        ipList = item['sourceIp'].split(", ")
        for ip in ipList:
            text = text + "Source-IP: "+str(ip)+"\r\n"

    text = text + "Arrival-Date: "+str(item['arrivalDate'])+" UTC\r\n"

    text = text + "Attachment-Password: "******"\r\n"

    if 'urlList' in item:
        for uri in item['urlList']:
            o = urlparse.urlparse(uri)
            urlReport=True
            if o.hostname is not None:
                for domain in wldomain:
                    if o.hostname[-len(domain):]==domain:
                        urlReport=False
                if urlReport==True:
                    text = text + "Reported-Uri: "+str(uri)+"\r\n"

    msgreport.set_payload(text)
    msg.attach(msgreport)

    #msgrfc822 = MIMEBase('message', "rfc822")
    msgrfc822 = MIMEBase('text', "rfc822-headers")
    msgrfc822.add_header('Content-Disposition','inline')
    parts=re.split(r'\r\n\r\n|\n\n',item['content'])
    rfc822headers=parts[0]
    #msgrfc822.set_payload(item['content'])
    msgrfc822.set_payload(rfc822headers)
    
    msg.attach(msgrfc822)

    #prepare the zip encrypted
    temp=tempfile.NamedTemporaryFile(prefix='mail',suffix='.eml',delete=False)
    tempname=temp.name
    temp.write(item['content'])
    temp.flush()
    ziptemp = tempfile.NamedTemporaryFile(prefix='mail',suffix='.zip',delete=True)
    ziptempname=ziptemp.name
    ziptemp.close()
    workdir = os.path.dirname(ziptempname)
    filenamezip = os.path.basename(ziptempname)
    filenameemail = os.path.basename(tempname)
    os.chdir(workdir)
    option = '-P%s' % arfPassword
    rc = subprocess.call(['zip', option] + [filenamezip, filenameemail])
    temp.close()

    
    ziptemp = open(ziptempname,"r")
    msgzip = MIMEBase('application', "zip")
    msgzip.set_payload(ziptemp.read())
    encoders.encode_base64(msgzip)
    msgzip.add_header('Content-Disposition', 'attachment', filename=filenamezip)
    ziptemp.close()

    msg.attach(msgzip)

    #delete created files
    os.remove(ziptempname)
    os.remove(tempname)


    #print "******************\r\n"
    #print msg.as_string()
    #print "******************\r\n"

    s = smtplib.SMTP(mailSmtp)
    # send to IP owners first
    if msg["To"] != "":
        toList = msg["To"].split(",")
        s.sendmail(msg["From"], toList, msg.as_string())
    # send a copy
    reportEmail=reportEmailCc
    if spam:
        reportEmail=reportEmailSpamCc
    if reportEmail != "":
        toList = reportEmail.split(",")
        for emailAddress in toList:
            if msg.has_key("To"):
                msg.replace_header("To",str(emailAddress))
            else:
                msg["To"]=str(emailAddress)
            s.sendmail(msg["From"], emailAddress, msg.as_string())
            
    s.quit()
Ejemplo n.º 17
0
class MimeSigningWrapper(MimeWrapper):
    CONTAINER_TYPE = 'multipart/signed'
    CONTAINER_PARAMS = ()
    SIGNATURE_TYPE = 'application/x-signature'
    SIGNATURE_DESC = 'Abstract Digital Signature'

    def __init__(self, *args, **kwargs):
        MimeWrapper.__init__(self, *args, **kwargs)

        name = 'signature.html' if self.use_html_wrapper else 'signature.asc'
        self.sigblock = MIMEBase(*self.SIGNATURE_TYPE.split('/'))
        self.sigblock.set_param("name", name)
        for h, v in (("Content-Description", self.SIGNATURE_DESC),
                     ("Content-Disposition",
                      "attachment; filename=\"%s\"" % name)):
            self.sigblock.add_header(h, v)

    def _wrap_sig_in_html(self, sig):
        return (
            "<html><body><h1>%(title)s</h1><p>\n\n%(description)s\n\n</p>"
            "<pre>\n%(sig)s\n</pre><hr>"
            "<i><a href='%(ad_url)s'>%(ad)s</a>.</i></body></html>"
            ) % self._wrap_sig_in_html_vars(sig)

    def _wrap_sig_in_html_vars(self, sig):
        return {
            # FIXME: We deliberately do not flag these messages for i18n
            #        translation, since we rely on 7-bit content here so as
            #        not to complicate the MIME structure of the message.
            "title": "Digital Signature",
            "description": (
                "This is a digital signature, which can be used to verify\n"
                "the authenticity of this message. You can safely discard\n"
                "or ignore this file if your e-mail software does not\n"
                "support digital signatures."),
            "ad": "Generated by Mailpile",
            "ad_url": "https://www.mailpile.is/",  # FIXME: Link to help?
            "sig": sig}

    def _update_crypto_status(self, part):
        part.signature_info.part_status = 'verified'

    def wrap(self, msg, prefer_inline=False):
        from_key = self.get_keys([self.sender])[0]

        if prefer_inline:
            prefer_inline = self.get_only_text_part(msg)
        else:
            prefer_inline = False

        if prefer_inline is not False:
            message_text = Normalize(prefer_inline.get_payload(None, True)
                                     .strip() + '\r\n\r\n')
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key,
                                             clearsign=True,
                                             armor=True)
            if status == 0:
                _update_text_payload(prefer_inline, sig)
                self._update_crypto_status(prefer_inline)
                return msg

        else:
            msg = self.prepare_wrap(msg)
            self.attach(msg)
            self.attach(self.sigblock)
            message_text = self.flatten(msg)
            status, sig = self.crypto().sign(message_text,
                                             fromkey=from_key, armor=True)
            if status == 0:
                if self.use_html_wrapper:
                    sig = self._wrap_sig_in_html(sig)
                self.sigblock.set_payload(sig)
                self._update_crypto_status(self.container)
                return self.container

        raise SignatureFailureError(_('Failed to sign message!'), from_key)