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!"))
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
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!'))
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
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)
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)
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
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
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
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)
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
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
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()
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
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()
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)