def send_mail(name, address, email): SERVER = "in-v3.mailjet.com" SERVER_PORT = "587" SUBJECT = "Purchase Summary" FROM = "*****@*****.**" PASSWD = "0459523f496bf2875f91890bf63b0e31" USER = "******" TO = email BODY = getBody(name, address) msg = MIMEMultipart() msg['Subject'] = SUBJECT msg['From'] = FROM msg['To'] = TO fp = open('reports/purchase_report.csv', 'rb') adj = MIMEBase('multipart', 'encrypted') adj.set_payload(fp.read()) fp.close() encoders.encode_quopri(adj) adj.add_header('Content-Disposition', 'attachment', filename='purchase_report.csv') msg.attach(MIMEText(BODY, 'html')) msg.attach(adj) TEXT = msg.as_string() server = smtplib.SMTP(SERVER, SERVER_PORT) server.starttls() server.login(USER, PASSWD) server.sendmail(FROM, TO, TEXT) server.quit()
def _multipart(self, depth, atchs, encts): base_msg = MIMEMultipart() for atch_id, encode_type in zip(atchs, encts): ctypes, encoding = mimetypes.guess_type( self.cfg[ATCH][atch_id]['file']) if not ctypes or encoding: ctypes = 'application/octet-stream' maintype, subtype = ctypes.split('/', 1) attach = MIMEBase(maintype, subtype) attach.set_payload(self.atch_files[atch_id]) if encode_type == 'base64': encoders.encode_base64(attach) elif encode_type == 'qp': encoders.encode_quopri(attach) else: encoders.encode_7or8bit(attach) attach.add_header('Content-Disposition', 'attachment', filename=self.cfg[ATCH][atch_id]['file']) base_msg.attach(attach) outer = [] outer.append(base_msg) for i in range(depth): outer.append(MIMEMultipart()) outer[-1].attach(outer[-2]) return outer[-1]
def set_part_content(part, content): # Reset old encoding and use quoted-printable (#5414) del part['Content-Transfer-Encoding'] part.set_payload(content) encode_quopri(part) return True
def check_html(msg,savname=None): "Remove scripts from HTML attachments." msgtype = msg.get_content_type().lower() # check for more MSIE braindamage if msgtype == 'application/octet-stream': for (attr,name) in msg.getnames(): if name and name.lower().endswith(".htm"): msgtype = 'text/html' if msgtype == 'text/html': out = StringIO() htmlfilter = HTMLScriptFilter(out) try: htmlfilter.write(msg.get_payload(decode=True).decode()) htmlfilter.close() #except sgmllib.SGMLParseError: except: mimetools.copyliteral(msg.get_payload(),open('debug.out','wb')) htmlfilter.close() hostname = socket.gethostname() msg.set_payload( "An HTML attachment could not be parsed. The original is saved as '%s:%s'" % (hostname,savname)) del msg["content-type"] del msg["content-disposition"] del msg["content-transfer-encoding"] name = "WARNING.TXT" msg["Content-Type"] = "text/plain; name="+name return Milter.CONTINUE if htmlfilter.modified: msg.set_payload(out) # remove embedded scripts del msg["content-transfer-encoding"] encoders.encode_quopri(msg) return Milter.CONTINUE
def to_message(mail): """ Given a MailBase message, this will construct a MIMEPart that is canonicalized for use with the Python email API. N.B. this changes the original email.message.Message """ ctype, params = mail.content_encoding['Content-Type'] if not ctype: if mail.parts: ctype = 'multipart/mixed' else: ctype = 'text/plain' else: if mail.parts: assert ctype.startswith("multipart") or ctype.startswith( "message" ), "Content type should be multipart or message, not %r" % ctype # adjust the content type according to what it should be now mail.content_encoding['Content-Type'] = (ctype, params) try: out = MIMEPart(ctype, **params) except TypeError as exc: raise EncodingError( "Content-Type malformed, not allowed: %r; %r (Python ERROR: %s" % (ctype, params, getattr(exc, "message", "(No error message)"))) for k in mail.keys(): if k in ADDRESS_HEADERS_WHITELIST: value = header_to_mime_encoding(mail[k]) else: value = header_to_mime_encoding(mail[k], not_email=True) if k.lower() in [key.lower() for key in CONTENT_ENCODING_KEYS]: del out[k] out[k] = value else: out[k] = value out.extract_payload(mail) # make sure payload respects cte cte, cte_params = mail.content_encoding['Content-Transfer-Encoding'] if cte == "quoted-printable": del out['Content-Transfer-Encoding'] encoders.encode_quopri(out) elif cte == "base64": del out['Content-Transfer-Encoding'] encoders.encode_base64(out) # go through the children for part in mail.parts: out.attach(to_message(part)) return out
def to_message(mail): """ Given a MailBase message, this will construct a MIMEPart that is canonicalized for use with the Python email API. N.B. this changes the original email.message.Message """ ctype, params = mail.content_encoding['Content-Type'] if not ctype: if mail.parts: ctype = 'multipart/mixed' else: ctype = 'text/plain' else: if mail.parts: assert ctype.startswith("multipart") or ctype.startswith("message"), \ "Content type should be multipart or message, not %r" % ctype # adjust the content type according to what it should be now mail.content_encoding['Content-Type'] = (ctype, params) try: out = MIMEPart(ctype, **params) except TypeError as exc: raise EncodingError("Content-Type malformed, not allowed: %r; %r (Python ERROR: %s" % (ctype, params, getattr(exc, "message", "(No error message)"))) for k in mail.keys(): if k in ADDRESS_HEADERS_WHITELIST: value = header_to_mime_encoding(mail[k]) else: value = header_to_mime_encoding(mail[k], not_email=True) if k.lower() in [key.lower() for key in CONTENT_ENCODING_KEYS]: del out[k] out[k] = value else: out[k] = value out.extract_payload(mail) # make sure payload respects cte cte, cte_params = mail.content_encoding['Content-Transfer-Encoding'] if cte == "quoted-printable": del out['Content-Transfer-Encoding'] encoders.encode_quopri(out) elif cte == "base64": del out['Content-Transfer-Encoding'] encoders.encode_base64(out) # go through the children for part in mail.parts: out.attach(to_message(part)) return out
def fix_encoding(msg): cte = 'Content-Transfer-Encoding' if msg[cte] == '8bit' and msg.get_content_maintype()=='text' and msg.get_charset() is None: charset = msg.get_content_charset() if not charset: return #broken payload = msg.get_payload(decode=False) msg.set_payload(payload) msg.set_charset(charset) if not sevenbit: return try: msg.get_payload().encode('ascii') except UnicodeError: encode_quopri(msg) else: if not cte in msg: msg.add_header(cte,'7bit')
def re_mime_encode(crypto_message): ''' Re-encode message if it was encoded with base64 or quoted printable. >>> from goodcrypto.mail.message.crypto_message import CryptoMessage >>> from goodcrypto.mail.message.email_message import EmailMessage >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name >>> with open(get_plain_message_name('basic.txt')) as input_file: ... crypto_message = CryptoMessage(email_message=EmailMessage(input_file)) ... re_mime_encode(crypto_message) False ''' decoded = re_encoded = False message = crypto_message.get_email_message().get_message() try: encoding = message.__getitem__( mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding is not None: encoding = encoding.lower() # only use the encoding if it's not a multipart message if (encoding == mime_constants.QUOTED_PRINTABLE_ENCODING or encoding == mime_constants.BASE64_ENCODING): current_content_type = message.get_content_type() if (current_content_type is not None and current_content_type.lower().find( mime_constants.MULTIPART_PRIMARY_TYPE) < 0): decoded = True log_message('payload decoded with {}'.format(encoding)) if decoded: if DEBUGGING: log_message('decoded message:\n{}'.format( crypto_message.get_email_message().get_message())) if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri(message) re_encoded = True elif encoding == mime_constants.BASE64_ENCODING: encode_base64(message) re_encoded = True crypto_message.get_email_message().set_message(message) log_message('payload re-encoded with {}'.format(encoding)) if DEBUGGING: log_message('encoded message:\n{}'.format( crypto_message.get_email_message().get_message())) return re_encoded
def _form_email(self, recipient_data): """ Form the html email, including mimetype and headers. """ # instatiate the email object and assign headers email_message = MIMEMultipart('related') email_message.preamble = 'This is a multi-part message in MIME format.' if self.txt_path != "": txt = MIMEText(PyMailer._prepare_text(self.txt_path, recipient_data), 'plain', _charset='utf-8') # encoders.encode_quopri(txt) email_message.attach(txt) if self.html_path != "": html = MIMEText(PyMailer._prepare_text(self.html_path, recipient_data), 'html', _charset='utf-8') # encoders.encode_quopri(html) email_message.attach(html) for image in self.images: with open(image, 'rb') as f: imageMime = MIMEImage(f.read()) imageMime.add_header('Content-ID', '<%s>' % image) email_message.attach(imageMime) for attachment in self.attachments: with open(attachment[1], 'rb') as f: attachmentMime = MIMENonMultipart(attachment[0].split('/')[0], attachment[0].split('/')[1]) attachmentMime.set_payload(f.read()) if attachment[0].split('/')[0] == 'text': encoders.encode_quopri(attachmentMime) attachmentMime.add_header( 'Content-Disposition', 'attachment; filename=%s' % attachment[1]) email_message.attach(attachmentMime) email_message['From'] = recipient_data.get('sender') email_message['To'] = recipient_data.get('recipient') email_message['Subject'] = self.subject return email_message.as_string()
def re_mime_encode(crypto_message): ''' Re-encode message if it was encoded with base64 or quoted printable. >>> from goodcrypto.mail.message.crypto_message import CryptoMessage >>> from goodcrypto.mail.message.email_message import EmailMessage >>> from goodcrypto_tests.mail.message_utils import get_plain_message_name >>> with open(get_plain_message_name('basic.txt')) as input_file: ... crypto_message = CryptoMessage(email_message=EmailMessage(input_file)) ... re_mime_encode(crypto_message) False ''' decoded = re_encoded = False message = crypto_message.get_email_message().get_message() try: encoding = message.__getitem__(mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding is not None: encoding = encoding.lower() # only use the encoding if it's not a multipart message if (encoding == mime_constants.QUOTED_PRINTABLE_ENCODING or encoding == mime_constants.BASE64_ENCODING): current_content_type = message.get_content_type() if (current_content_type is not None and current_content_type.lower().find(mime_constants.MULTIPART_PRIMARY_TYPE) < 0): decoded = True log_message('payload decoded with {}'.format(encoding)) if decoded: if DEBUGGING: log_message('decoded message:\n{}'.format( crypto_message.get_email_message().get_message())) if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri(message) re_encoded = True elif encoding == mime_constants.BASE64_ENCODING: encode_base64(message) re_encoded = True crypto_message.get_email_message().set_message(message) log_message('payload re-encoded with {}'.format(encoding)) if DEBUGGING: log_message('encoded message:\n{}'.format( crypto_message.get_email_message().get_message())) return re_encoded
def _prepare_message(txt): """ Apply the proper encoding to all email fields. The body will be Latin-1, the headers will be 'quopri'd when necessary. """ def plain(val): """ Return True when val is plain ASCII """ try: val.decode('ascii') return True except: return False # Use Latin-1 because not all email clients know UTF-8. code = 'ISO-8859-1' msg = Message() payload = [] body = False header = False for line in txt.encode(code, 'replace').split('\n'): if header and not line: body = True if body: payload.append(line) else: m = RE_HEADER.search(line) if m: header = True keyword = m.group(1).strip() value = m.group(2).strip() if plain(value): # Don't encode if not needed, because some email clients # choke when headers like "date" are encoded. msg.add_header(keyword, value) else: header = Header(value, code) msg[keyword] = header msg.set_payload('\n'.join(payload), code) # Check for proper encoding, else call it explicitly if not msg.has_key('Content-Transfer-Encoding'): encode_quopri(msg) return msg.as_string()
def send(self, msg_body, mto, mfrom=None, mbcc=None, reply_to=None, subject=None, encode=None, immediate=False, charset=None, msg_type=None): """Sends a message defined at least by its message body and the destination address. """ mhost = self.context.MailHost try: # Plone 4 msg = message_from_string(msg_body.encode(charset)) if encode is None or encode in ["quoted-printable", "qp"]: encode_quopri(msg) else: encode_base64(msg) msg['BCC']= Header(mbcc) if reply_to: msg['Reply-To'] = Header(reply_to) mhost.send(msg, mto=mto, mfrom=mfrom, subject=subject, encode=encode, immediate=immediate, msg_type=msg_type, charset=charset) except (TypeError, NameError): # Plone 3 or earlier subtype = msg_type.split('/')[1] mhost.secureSend(msg_body, mto=mto, mfrom=mfrom, subject=subject, mbcc=mbcc, subtype=subtype, charset=charset) except (MailHostError, socket.error), e: logger.error("sending mail with subject %s to %s failed: %s." % (subject, mto, str(e)))
def _build_mail_to_encrypt(message: str, files: list) -> MIMEMultipart: """ Create the MIMEMultipart mail containing the text message and the potentials attachments. :param message: The text message of the encrypted email. :param files: The files to attach and encrypt. :return: A MIMEMultipart mail object. """ mail_to_encrypt = MIMEMultipart() mail_to_encrypt.policy = policy.SMTPUTF8 if message == '--': message = sys.stdin.read() message_mail = MIMEBase('text', 'plain', charset='UTF-8') message_mail.policy = policy.SMTPUTF8 message_mail.set_payload(message.encode('UTF-8')) encoders.encode_quopri(message_mail) mail_to_encrypt.attach(message_mail) if files: for file in files: path = Path(file) guessed_type = mimetypes.guess_type(path.absolute().as_uri())[0] if not guessed_type: print('Could not guess file %s mime-type, using application/octet-stream.' % file, file=sys.stderr) guessed_type = 'application/octet-stream' mimetype = guessed_type.split('/') mail_attachment = MIMEBase(mimetype[0], mimetype[1]) mail_attachment.policy = policy.SMTPUTF8 mail_attachment.set_payload(open(str(path.absolute()), 'rb').read()) encoders.encode_base64(mail_attachment) mail_attachment.add_header('Content-Disposition', "attachment", filename=path.name) mail_to_encrypt.attach(mail_attachment) return mail_to_encrypt
def send(self, message, page=None, name=None, email=None, honeypot=None): # Detect if a spambot has just filled all form fields. if honeypot is not None: self.redirect(self.url('feedback-accepted-honeypot')) return page = page or u"" name = name or u"UNKNOWN" email = email or u"EMPTY-EMAIL" message_body = self.mail_template % dict( message=message, page=page, name=name, email=email, root=self.application_url(), ) message = email_message.Message() message.set_payload(message_body.encode('utf-8')) subject_header = email_header.Header( (u'Assembly Archive feedback about "%s"' % page).encode('utf-8'), 'utf-8') message['Subject'] = subject_header message['To'] = self.target_address from_header = email_header.Header( (u'%s <%s>' % (name, email)).encode('utf-8'), 'utf-8') message['From'] = from_header email_encoders.encode_quopri(message) message.set_charset('utf-8') message_str = message.as_string() smtp = smtplib.SMTP(self.smtp_host) smtp.sendmail(self.target_address, [self.target_address], message_str) self.flash(u'Your feedback was accepted.') self.redirect(self.url('feedback-accepted'))
<li><b>Resolves to:</b> {RESOLV}</li> <li><b>Time of occurance:</b> {TIME}</li> <li><b>Environment Variable:</b> {ENVIRON}</li> <li><b>Parameters:</b> {PARAMS}</li> <li><b>Logged On Users:</b> {USERS}</li> </ul> </div> </body> </html>""".format(IP=IP, RESOLV=RESOLV, TIME=time(), ENVIRON=os.environ, PARAMS=argv, USERS=psutil.users(), SUBJECT="SSH Login: {}@{}".format(USER, DOMAIN)) email_body_text = MIMEText(text, 'plain') #email_body_html = MIMEText(html, 'html') email_body_html = MIMEBase('text', 'html') email_body_html.set_payload(html) encoders.encode_quopri(email_body_html) email_body_html.set_charset('UTF-8') email.attach(email_body_text) email.attach(email_body_html) if not PROXY_MAIL: for mx_record in dns.resolver.query(TO_DOMAIN, 'MX'): mail_server = mx_record.to_text().split()[1][:-1] try: server = smtplib.SMTP(mail_server) server.sendmail(FROM, '{}@{}'.format(TO), email.as_string()) server.close() break except Exception as e: log.warning("Could not notify our chief of command @ {}!!".format(mail_server))
value = header_to_mime_encoding(mail[k]) else: value = header_to_mime_encoding(mail[k], not_email=True) if k.lower() in [key.lower() for key in CONTENT_ENCODING_KEYS]: del out[k] out[k] = value else: out[k.encode('ascii')] = value out.extract_payload(mail) # make sure payload respects cte cte, cte_params = mail.content_encoding['Content-Transfer-Encoding'] if cte == "quoted-printable": encoders.encode_quopri(out) elif cte == "base64": encoders.encode_base64(out) # go through the children for part in mail.parts: out.attach(to_message(part)) return out def to_string(mail, envelope_header=False): """Returns a canonicalized email string you can use to send or store somewhere.""" msg = to_message(mail).as_string(envelope_header) assert "From nobody" not in msg
def _decrypt_inline_pgp(self, from_user, crypto, passcode): ''' Decrypt an inline PGP message (internal use only). ''' def adjust_attachment_name(part): '''Adjust the filename for the attachment.''' try: filename = part.get_filename() if filename and filename.endswith('.pgp'): self.log_message( 'original attachment filename: {}'.format(filename)) end = len(filename) - len('.pgp') part.replace_header( mime_constants.CONTENT_DISPOSITION_KEYWORD, 'attachment; filename="{}"'.format(filename[:end])) filename = part.__getitem__( mime_constants.CONTENT_DISPOSITION_KEYWORD) self.log_message( 'new attachment filename: {}'.format(filename)) else: self.log_message( 'attachment filename: {}'.format(filename)) except: record_exception() self.log_message( 'EXCEPTION - see syr.exception.log for details') decrypted = False self.log_message("message is inline PGP format") message = self.crypto_message.get_email_message().get_message() self.log_message("message content type is {}".format( message.get_content_type())) # remove any clear signed section before decrypting message self.crypto_message.get_email_message().remove_pgp_signature_blocks() if message.is_multipart(): for part in message.get_payload(): content_type = part.get_content_type() ciphertext = part.get_payload(decode=True) charset, __ = get_charset(part) self.log_message( 'multipart message char set: {}'.format(charset)) plaintext = self._decrypt(from_user, ciphertext, charset, crypto, passcode) if plaintext is not None and plaintext != ciphertext: decrypted = True charset, __ = part.get_charset() self.log_message( "message part charset is {}".format(charset)) part.set_payload(plaintext, charset=charset) try: content_keyword_in_part = part.__getitem__( mime_constants.CONTENT_DISPOSITION_KEYWORD) except Exception: content_keyword_in_part = None if content_keyword_in_part is not None: adjust_attachment_name(part) try: encoding = part.__getitem__( mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri(part) self.log_message( "{} encoded message part".format(encoding)) elif encoding == mime_constants.BASE64_ENCODING: encode_base64(part) self.log_message( "{} encoded message part".format(encoding)) else: charset, __ = get_charset(self.crypto_message.get_email_message()) self.log_message('inline pgp char set: {}'.format(charset)) ciphertext = self.crypto_message.get_email_message().get_content() plaintext = self._decrypt(from_user, ciphertext, charset, crypto, passcode) if plaintext is None or ciphertext is None or plaintext == ciphertext: decrypted = False self.log_message("unable to decrypt {} message".format( message.get_content_type())) else: # do not specify the codec self.crypto_message.get_email_message().get_message( ).set_payload(plaintext) try: encoding = self.crypto_message.get_email_message( ).get_message().__getitem__( mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri( self.crypto_message.get_email_message().get_message()) elif encoding == mime_constants.BASE64_ENCODING: encode_base64( self.crypto_message.get_email_message().get_message()) decrypted = True return decrypted
def construct_and_send_email(self): """Do work of constructing & sending reply email. For internal use.""" # who to respond to? to_email = None if 'Reply-To' in self.message: to_email = self.message['Reply-To'] elif 'From' in self.message: to_email = self.message['From'] if not to_email: log.warn('Cannot decide who to respond to') return # XXX throw exception instead # what the response subject should be if 'Subject' in self.message: if self.message['Subject'][:4] != 'Re: ': subject = 'Re: '+self.message['Subject'] else: subject = self.message['Subject'] else: subject = 'CryptoBot response' from_email = '{0} <{1}>'.format(config.pgp_name, config.pgp_email) # start the email msg = MIMEMultipart('mixed') body = MIMEMultipart('alternative') template_vars = { 'encrypted_right': self.message.encrypted_right, 'encrypted_wrong': self.message.encrypted_wrong, 'signed': self.message.signed, 'pubkey_included': self.message.pubkey_included, 'pubkey_included_wrong': self.message.pubkey_included_wrong, 'pubkey_fingerprint': self.message.pubkey_fingerprint } body_txt = self.txt_template.render(template_vars) txt_part = MIMEBase('text', 'plain') txt_part.set_payload(body_txt) encode_quopri(txt_part) body.attach(txt_part) body_html = self.html_template.render(template_vars) html_part = MIMEBase('text', 'html') html_part.set_payload(body_html) encode_quopri(html_part) body.attach(html_part) msg.attach(body) # if the message is not encrypted, attach public key (#16) if not self.message.encrypted_right: pubkey = str(self._gpg.export_keys(self.fp)) pubkey_filename = '{0} {1} (0x{2}) pub.asc'.format(config.pgp_name, config.pgp_email, str(self.fp)[:-8]) pubkey_part = MIMEBase('application', 'pgp-keys') pubkey_part.add_header('Content-Disposition', 'attachment; filename="%s"' % pubkey_filename) pubkey_part.set_payload(pubkey) encode_quopri(pubkey_part) msg.attach(pubkey_part) # sign the message msg_string = (self.as_string(msg)+'\n').replace('\n', '\r\n') sig = self._gpg.sign(msg_string) # make a sig part sig_part = MIMEBase('application', 'pgp-signature', name='signature.asc') sig_part.add_header('Content-Description', 'OpenPGP digital signature') sig_part.add_header('Content-Disposition', 'attachment; filename="signature.asc"') sig_part.set_payload(sig) # wrap it all up in multipart/signed signed = MIMEMultipart(_subtype="signed", micalg="pgp-sha1", protocol="application/pgp-signature") signed.attach(msg) signed.attach(sig_part) # if we're just signing and not encrypting this message, add the headers directly to the signed part if not self.message.pubkey_fingerprint: signed['Subject'] = subject signed['From'] = from_email signed['To'] = to_email # need to add a '\r\n' right before the sig part (#19) # because of this bug http://bugs.python.org/issue14983 signed_string = self.as_string(signed) i = signed_string.rfind('--', 0, signed_string.find('Content-Type: application/pgp-signature; name="signature.asc"')) signed_string = signed_string[0:i]+'\r\n'+signed_string[i:] # if we have a fingerprint to encrypt to, encrypt it if self.message.pubkey_fingerprint: # encrypt the message ciphertext = self._gpg.encrypt(signed_string, self.message.pubkey_fingerprint) # make an application/pgp-encrypted part encrypted = MIMEBase("application", "pgp-encrypted") encrypted.set_payload("Version: 1\r\n") # make application/octet-stream part octet_stream = MIMEBase("application", "octet-stream") octet_stream.set_payload(ciphertext) # make the multipart/encrypted wrapper wrapper = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted") wrapper.attach(encrypted) wrapper.attach(octet_stream) # add headers to the encryption wrapper wrapper['Subject'] = subject wrapper['From'] = from_email wrapper['To'] = to_email final_message = self.as_string(wrapper) else: final_message = signed_string self.send_email(final_message, from_email, to_email) log.info('Responded to %s: %r', self.message['From'], template_vars)
def Compile(self): """Check message for error and compile it""" # self._Message = Message() self._Message = MIMEMultipart() m = self._Message if len(self._Body) == 0: raise EmptyBody("Please set Body") if len(self._Recipients['To']) + len(self._Recipients['Cc']) + len(self._Recipients['Bcc']) == 0: raise EmptyAddr("Recipients address not set") # if len(self._AltBody) > 0: # self.ContentType = "multipart/alternative" if len(self._MessageDate) != 0: m.add_header("Date", self._MessageDate) else: m.add_header("Date", time.strftime("%a, %d %b %Y %T %z")) if len(self._ReturnPath) != 0: m.add_header("Return-path", self._ReturnPath) elif len(self._Sender) != 0: m.add_header("Return-path", self._Sender) else: m.add_header("Return-path", self._From) self._Sender = self._From for rcpt in self._Recipients.keys(): if len(self._Recipients[rcpt]) > 0: for each in self._Recipients[rcpt]: if len(each['text']) > 0: m.add_header(rcpt, '"%s" <%s>' % (str(Header(each['text'], 'utf-8')), each['email'])) else: m.add_header(rcpt, '<%s>' % (each['email'])) if m.get("To") in (None, ''): m.add_header("To", 'undisclosed-recipients:;') m.add_header("From", self._From) if self._ReplyTo != '': reply_to = self._ReplyTo if len(reply_to['text']) > 0: m.add_header("Reply-To", '<%s>' (reply_to['email'])) else: m.add_header("Reply-To", '"%s" <%s>' % (str(Header(reply_to['text'], 'utf-8')), reply_to['email'])) if len(self._MessageID) != 0: m.add_header("Message-ID", self._MessageID) else: m.add_header("Message-ID", '<%s@%s>' % (self.uniqid(), self._Hostname)) m.add_header('X-Priority', str(self._Priority)) m.add_header("X-Mailer", self._XMailer) m.add_header("Subject", str(Header(self._Subject, 'utf-8'))) if len(self._AltBody) > 0: # alt_body = MIMEText(self._AltBody, ) # alt_body.set_type(self._AltBodyType) alt_body = MIMEBase(self._AltBodyType.split('/')[0], self._AltBodyType.split('/')[1]) alt_body.set_payload(self._AltBody) if self._Encoding == 'base64': encoders.encode_base64(alt_body) elif self._Encoding == 'quoted': encoders.encode_quopri(alt_body) elif self._Encoding in ('8bit', '7bit'): encoders.encode_7or8bit(alt_body) alt_body.set_charset(self._CharSet) # body = MIMEText(self._Body) # body.set_type(self._BodyType) body = MIMEBase(self._BodyType.split('/')[0], self._BodyType.split('/')[1]) body.set_payload(self._Body) if self._Encoding == 'base64': encoders.encode_base64(body) elif self._Encoding == 'quoted': encoders.encode_quopri(body) elif self._Encoding in ('8bit', '7bit'): encoders.encode_7or8bit(body) body.set_charset(self._CharSet) m.attach(alt_body) m.attach(body) else: # body = MIMEText(self._Body) # body.set_type(self._BodyType) body = MIMEBase(self._BodyType.split('/')[0], self._BodyType.split('/')[1]) body.set_payload(self._Body) encoders.encode_base64(body) body.set_charset(self._CharSet) m.attach(body) # m.set_charset(self._CharSet) m.set_type('multipart/alternative') self._isCompile = True
def send(self, emails): if isinstance(emails, Email): emails = [emails] if len([e for e in emails if e.__class__ != Email]): raise TypeError('emails must be Email or list of Email instances') smtpclass = SMTP_SSL if self.ssl else SMTP if self.server == 'localhost': smtp = smtpclass(self.server) else: smtp = smtpclass(self.server, self.port) if self.login and self.password: smtp.login(self.login, self.password) for email in emails: c = Charset(email.charset) c.header_encoding = QP c.body_encoding = 0 r = Charset(email.charset) r.header_encoding = 0 r.body_encoding = 0 mime1, mime2 = email.mimetype.split('/') mainpart = MIMEBase(mime1, mime2) if not email.force_7bit: mainpart.set_param('charset', email.charset) if len(email.attachments): message = MIMEMultipart('mixed') message.attach(mainpart) del mainpart['mime-version'] else: message = mainpart message['Date'] = datetime.datetime.now().strftime( '%a, %d %b %Y %H:%M:%S') + (" +%04d" % (time.timezone / -36, )) h = Header() fromname = self.fromname.encode(email.charset, 'xmlcharrefreplace') h.append(fromname, r if is7bit(fromname) else c) h.append('<%s>' % self.email, r) message['From'] = h message['To'] = email.get_emails_header('rcpt') if len(email.cc): message['CC'] = email.get_emails_header('cc') if len(email.bcc): message['BCC'] = email.get_emails_header('bcc') subject = email.subject.encode(email.charset, 'xmlcharrefreplace') message['Subject'] = Header(subject, r if is7bit(subject) else c) if email.force_7bit: body = email.body.encode('ascii', 'xmlcharrefreplace') else: body = email.body.encode(email.charset, 'xmlcharrefreplace') mainpart.set_payload(body) for hn, hv in email.headers.items(): if hn == 'X-Mailgun-Campaign-Tag': hn = 'X-Mailgun-Tag' message[hn] = hv if is7bit(body): mainpart['Content-Transfer-Encoding'] = '7bit' else: encode_quopri(mainpart) for attachment in email.attachments: if attachment.__class__ != Attachment: raise TypeError("invalid attachment") mimetype = attachment.mimetype if not mimetype: mimetype, encoding = guess_type(attachment.filename) if not mimetype: mimetype = 'application/octet-stream' mime1, mime2 = mimetype.split('/') part = MIMEBase(mime1, mime2) # using newer rfc2231 (not supported by Outlook): # part.set_param('name', attachment.filename.encode('utf-8'), charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('name', str(Header(attachment.filename))) del part['mime-version'] if attachment.id: part['Content-Disposition'] = 'inline' else: part['Content-Disposition'] = 'attachment' # using newer rfc2231 (not supported by Outlook): # part.set_param('filename', # attachment.filename.encode('utf-8'), # 'Content-Disposition', # charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('filename', str(Header(attachment.filename)), 'Content-Disposition') if attachment.id: part['Content-ID'] = '<%s>' % attachment.id part.set_payload(attachment.content) encode_base64(part) message.attach(part) emails = email.rcpt + email.cc + email.bcc smtp.sendmail(self.email, [email for _, email in emails], message.as_string()) smtp.quit()
def email(configuration): if not 'TEXT_TEMPLATE' in configuration or configuration[ 'TEXT_TEMPLATE'] is None: return None if not 'HTML_TEMPLATE' in configuration or configuration[ 'HTML_TEMPLATE'] is None: return None log('Sending e-mail from <{SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN}> to <{SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}>' .format(**configuration), level=LOG_LEVELS.DEBUG) configuration['HASH'] = md5( bytes( '{SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN}+{SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}' .format(**configuration), 'UTF-8')).hexdigest() configuration[ 'Message-ID'] = '<{RAW_TIME}.{HASH}@{SSH_MAIL_USER_FROMDOMAIN}>'.format( **configuration) ## TODO: https://support.google.com/mail/answer/81126 ## TODO:(DKIM) https://russell.ballestrini.net/quickstart-to-dkim-sign-email-with-python/ ## TODO:(S/MIME) https://tools.ietf.org/doc/python-m2crypto/howto.smime.html ## TODO: https://support.rackspace.com/how-to/create-an-spf-txt-record/ ## ## https://toolbox.googleapps.com/apps/checkmx/check?domain={DOMAIN}&dkim_selector= ## https://github.com/PowerDNS/pdns/issues/2881 email = MIMEMultipart('alternative') email['Subject'] = configuration['SUBJECT'] email[ 'From'] = "{SSH_MAIL_USER_FROM} <{SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN}>".format( **configuration) email['To'] = "<{SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}>".format( **configuration) email['Message-ID'] = configuration['Message-ID'] email.preamble = configuration['SUBJECT'] text = configuration['TEXT_TEMPLATE'].format(**configuration) html = configuration['HTML_TEMPLATE'].format(**configuration) email_body_text = MIMEText(text, 'plain') email_body_html = MIMEBase('text', 'html') email_body_html.set_payload(html) encoders.encode_quopri(email_body_html) email_body_html.set_charset('UTF-8') email.attach(email_body_text) email.attach(email_body_html) email["DKIM-Signature"] = sign_email(email, configuration) context = ssl.create_default_context() for mx_record in dns.resolver.query( configuration['SSH_MAIL_USER_TODOMAIN'], 'MX'): mail_server = mx_record.to_text().split()[1][:-1] try: server = smtplib.SMTP(mail_server, local_hostname='hvornum.se', port=25, timeout=10) # 587 = TLS, 465 = SSL if server.starttls(context=context)[0] != 220: log('Could not start TLS.', level=3, origin='mailer') configuration['mail_server'] = mail_server log('Mail via {mail_server} | from {SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN} -> {SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}' .format(**configuration), origin='mailer', level=LOG_LEVELS.DEBUG) server.sendmail( '{SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN}'.format( **configuration), '{SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}'.format( **configuration), email.as_string()) server.quit() # server.close() configuration['mail_server'] = mail_server log("Sent email from {SSH_MAIL_USER_FROM}@{SSH_MAIL_USER_FROMDOMAIN} to {SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN} about via {mail_server}." .format(**configuration), level=LOG_LEVELS.INFO, origin='mailer') return True except Exception as e: log("Could not send email via: {}!!".format(mail_server), level=3, origin='mailer') log("{}".format(e), level=3, origin='mailer') if configuration['TRY_ONE_MAILSERVER']: break return False
email = MIMEMultipart('alternative') email['Subject'] = configuration['SUBJECT'] email['From'] = "SSH Guard <{SSH_MAIL_USER_FROM}@{DOMAIN}>".format(**configuration) email['To'] = "{SSH_MAIL_TO_REALNAME} <{SSH_MAIL_USER_TO}@{SSH_MAIL_USER_TODOMAIN}>".format(**configuration) email['Message-ID'] = configuration['Message-ID'] email.preamble = configuration['SUBJECT'] text = load_text_template() html = load_html_template() email_body_text = MIMEText(text, 'plain') email_body_html = MIMEBase('text', 'html') email_body_html.set_payload(html) encoders.encode_quopri(email_body_html) email_body_html.set_charset('UTF-8') email.attach(email_body_text) email.attach(email_body_html) email["DKIM-Signature"] = sign_email(email) context = ssl.create_default_context() for mx_record in dns.resolver.query('gmail.com', 'MX'): mail_server = mx_record.to_text().split()[1][:-1] try: server = smtplib.SMTP(mail_server, port=25, timeout=10) # 587 = TLS, 465 = SSL if server.starttls(context=context)[0] != 220: log.warning('Could not start TLS.')
def _decrypt_inline_pgp(self, from_user, crypto, passcode): ''' Decrypt an inline PGP message (internal use only). ''' def adjust_attachment_name(part): '''Adjust the filename for the attachment.''' try: filename = part.get_filename() if filename and filename.endswith('.pgp'): self.log_message('original attachment filename: {}'.format(filename)) end = len(filename) - len('.pgp') part.replace_header( mime_constants.CONTENT_DISPOSITION_KEYWORD, 'attachment; filename="{}"'.format(filename[:end])) filename = part.__getitem__(mime_constants.CONTENT_DISPOSITION_KEYWORD) self.log_message('new attachment filename: {}'.format(filename)) else: self.log_message('attachment filename: {}'.format(filename)) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') decrypted = False self.log_message("message is inline PGP format") message = self.crypto_message.get_email_message().get_message() self.log_message("message content type is {}".format(message.get_content_type())) # remove any clear signed section before decrypting message self.crypto_message.get_email_message().remove_pgp_signature_blocks() if message.is_multipart(): for part in message.get_payload(): content_type = part.get_content_type() ciphertext = part.get_payload(decode=True) charset, __ = get_charset(part) self.log_message('multipart message char set: {}'.format(charset)) plaintext = self._decrypt(from_user, ciphertext, charset, crypto, passcode) if plaintext is not None and plaintext != ciphertext: decrypted = True charset, __ = part.get_charset() self.log_message("message part charset is {}".format(charset)) part.set_payload(plaintext, charset=charset) try: content_keyword_in_part = part.__getitem__(mime_constants.CONTENT_DISPOSITION_KEYWORD) except Exception: content_keyword_in_part = None if content_keyword_in_part is not None: adjust_attachment_name(part) try: encoding = part.__getitem__(mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri(part) self.log_message("{} encoded message part".format(encoding)) elif encoding == mime_constants.BASE64_ENCODING: encode_base64(part) self.log_message("{} encoded message part".format(encoding)) else: charset, __ = get_charset(self.crypto_message.get_email_message()) self.log_message('inline pgp char set: {}'.format(charset)) ciphertext = self.crypto_message.get_email_message().get_content() plaintext = self._decrypt(from_user, ciphertext, charset, crypto, passcode) if plaintext is None or ciphertext is None or plaintext == ciphertext: decrypted = False self.log_message("unable to decrypt {} message".format(message.get_content_type())) else: # do not specify the codec self.crypto_message.get_email_message().get_message().set_payload(plaintext) try: encoding = self.crypto_message.get_email_message().get_message().__getitem__( mime_constants.CONTENT_XFER_ENCODING_KEYWORD) except Exception: encoding = None if encoding == mime_constants.QUOTED_PRINTABLE_ENCODING: encode_quopri(self.crypto_message.get_email_message().get_message()) elif encoding == mime_constants.BASE64_ENCODING: encode_base64(self.crypto_message.get_email_message().get_message()) decrypted = True return decrypted
def send(self, emails): if isinstance(emails, Email): emails = [emails] if len([e for e in emails if e.__class__ != Email]): raise TypeError('emails must be Email or list of Email instances') smtpclass = SMTP_SSL if self.ssl else SMTP if self.server == 'localhost': smtp = smtpclass(self.server) else: smtp = smtpclass(self.server, self.port) if self.tls: smtp.starttls() if self.login and self.password: smtp.login(self.login, self.password) for email in emails: c = Charset(email.charset) c.header_encoding = QP c.body_encoding = 0 r = Charset(email.charset) r.header_encoding = 0 r.body_encoding = 0 email.normalize_email_list('rcpt') email.normalize_email_list('cc') email.normalize_email_list('bcc') mime1, mime2 = email.mimetype.split('/') mainpart = MIMEBase(mime1, mime2) if not email.force_7bit: mainpart.set_param('charset', email.charset) if len(email.attachments): message = MIMEMultipart('mixed') message.attach(mainpart) del mainpart['mime-version'] else: message = mainpart message['Date'] = datetime.datetime.now().strftime( '%a, %d %b %Y %H:%M:%S') + (" +%04d" % (time.timezone/-36,)) h = Header(maxlinelen=1000) # FIXME: what is correct max length? fromname = self.fromname.encode(email.charset, 'xmlcharrefreplace') h.append(fromname, r if is7bit(fromname) else c) h.append('<%s>' % self.email, r) message['From'] = h message['To'] = email.get_emails_header('rcpt') if len(email.cc): message['CC'] = email.get_emails_header('cc') if len(email.bcc): message['BCC'] = email.get_emails_header('bcc') subject = email.subject.encode(email.charset, 'xmlcharrefreplace') message['Subject'] = Header(subject, r if is7bit(subject) else c) if email.reply_to: message['Reply-To'] = email.get_emails_header('reply_to') if email.force_7bit: body = email.body.encode('ascii', 'xmlcharrefreplace') else: body = email.body.encode(email.charset, 'xmlcharrefreplace') mainpart.set_payload(body) if is7bit(body): mainpart['Content-Transfer-Encoding'] = '7bit' else: encode_quopri(mainpart) for attachment in email.attachments: if attachment.__class__ != Attachment: raise TypeError("invalid attachment") mimetype = attachment.mimetype if not mimetype: mimetype, encoding = guess_type(attachment.filename) if not mimetype: mimetype = 'application/octet-stream' mime1, mime2 = mimetype.split('/') part = MIMEBase(mime1, mime2) # using newer rfc2231 (not supported by Outlook): # part.set_param('name', attachment.filename.encode('utf-8'), charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('name', str(Header(attachment.filename))) del part['mime-version'] if attachment.id: part['Content-Disposition'] = 'inline' else: part['Content-Disposition'] = 'attachment' # using newer rfc2231 (not supported by Outlook): # part.set_param('filename', # attachment.filename.encode('utf-8'), # 'Content-Disposition', # charset = 'utf-8') # hack: using deprecated rfc2047 - supported by Outlook: part.set_param('filename', str(Header(attachment.filename)), 'Content-Disposition') if attachment.id: part['Content-ID'] = '<%s>' % attachment.id part.set_payload(attachment.content) encode_base64(part) # Do this AFTER encode_base64(part), or Content-Transfer-Encoding header will duplicate, # or even happen 2 times with different values. if attachment.charset: part.set_charset(attachment.charset) message.attach(part) smtp.sendmail(self.email, [rcpt[1] for rcpt in email.rcpt] + [cc[1] for cc in email.cc] + [bcc[1] for bcc in email.bcc], message.as_string()) smtp.quit()
def construct_and_send_email(self): """Do work of constructing & sending reply email. For internal use.""" # who to respond to? to_email = self.message.sender_address if not to_email: logging.error('Cannot decide who to respond to in %s' % (self.message_id())) return # XXX throw exception instead # what the response subject should be if 'Subject' in self.message: if self.message['Subject'][:4] != 'Re: ': subject = 'Re: ' + self.message['Subject'] else: subject = self.message['Subject'] else: subject = 'CryptoBot response' from_email = '{0} <{1}>'.format(config.PGP_NAME, config.PGP_EMAIL) # start the email msg = MIMEMultipart('mixed') body = MIMEMultipart('alternative') template_vars = { 'encrypted_right': self.message.encrypted_right, 'encrypted_wrong': self.message.encrypted_wrong, 'signed': self.message.signed, 'pubkey_included': self.message.pubkey_included, 'pubkey_included_wrong': self.message.pubkey_included_wrong, 'pubkey_fingerprint': self.message.pubkey_fingerprint } body_txt = self.txt_template.render(template_vars) txt_part = MIMEBase('text', 'plain') txt_part.set_payload(body_txt) encode_quopri(txt_part) body.attach(txt_part) body_html = self.html_template.render(template_vars) html_part = MIMEBase('text', 'html') html_part.set_payload(body_html) encode_quopri(html_part) body.attach(html_part) msg.attach(body) # if the message is not encrypted, attach public key (#16) if not self.message.encrypted_right: pubkey = str(self._gpg.export_keys(self.fingerprint)) pubkey_filename = '{0} {1} (0x{2}) pub.asc'.format( config.PGP_NAME, config.PGP_EMAIL, str(self.fingerprint)[:-8]) pubkey_part = MIMEBase('application', 'pgp-keys') pubkey_part.add_header( 'Content-Disposition', 'attachment; filename="%s"' % pubkey_filename) pubkey_part.set_payload(pubkey) encode_quopri(pubkey_part) msg.attach(pubkey_part) # sign the message msg_string = (self.as_string(msg) + '\n').replace('\n', '\r\n') sig = self._gpg.sign(msg_string) # make a sig part sig_part = MIMEBase('application', 'pgp-signature', name='signature.asc') sig_part.add_header('Content-Description', 'OpenPGP digital signature') sig_part.add_header('Content-Disposition', 'attachment; filename="signature.asc"') sig_part.set_payload(sig) # wrap it all up in multipart/signed signed = MIMEMultipart(_subtype="signed", micalg="pgp-sha1", protocol="application/pgp-signature") signed.attach(msg) signed.attach(sig_part) # if we're just signing and not encrypting this message, add the headers directly to the signed part if not self.message.pubkey_fingerprint: signed['Subject'] = subject signed['From'] = from_email signed['To'] = to_email # need to add a '\r\n' right before the sig part (#19) # because of this bug http://bugs.python.org/issue14983 signed_string = self.as_string(signed) i = signed_string.rfind( '--', 0, signed_string.find( 'Content-Type: application/pgp-signature; name="signature.asc"' )) signed_string = signed_string[0:i] + '\r\n' + signed_string[i:] # if we have a fingerprint to encrypt to, encrypt it if self.message.pubkey_fingerprint: # encrypt the message ciphertext = self._gpg.encrypt(signed_string, self.message.pubkey_fingerprint) # make an application/pgp-encrypted part encrypted = MIMEBase("application", "pgp-encrypted") encrypted.set_payload("Version: 1\r\n") # make application/octet-stream part octet_stream = MIMEBase("application", "octet-stream") octet_stream.set_payload(ciphertext) # make the multipart/encrypted wrapper wrapper = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted") wrapper.attach(encrypted) wrapper.attach(octet_stream) # add headers to the encryption wrapper wrapper['Subject'] = subject wrapper['From'] = from_email wrapper['To'] = to_email final_message = self.as_string(wrapper) else: final_message = signed_string self.sender(final_message, from_email, to_email) logging.info('Responded to {0} {1}'.format(self.message['From'], str(template_vars)))