def process(self, baton): message = util.dict_get_path(baton, self.message_path) for key, value in self.headers.items(): if key in message: message.replace_header(key, value) else: message.add_header(key, value) return baton
def encrypt_all_payloads(message, gpg_to_cmdline): encrypted_payloads = list() if type(message.get_payload()) == str: if ( cfg.has_key("default") and cfg["default"].has_key("mime_conversion") and cfg["default"]["mime_conversion"] == "yes" ): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param("PGP/MIME version identification", "", "Content-Description") submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param("name", "encrypted.asc") submsg2.set_param("OpenPGP encrypted message", "", "Content-Description") submsg2.set_param("inline", "", "Content-Disposition") submsg2.set_param("filename", "encrypted.asc", "Content-Disposition") # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. submsg2.set_payload("\n" + message.get_payload()) message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key("Content-Type"): message.replace_header( "Content-Type", 'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary, ) else: message["Content-Type"] = ( 'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary ) return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline)] else: # Do a simple in-line PGP conversion of a plain text email. return encrypt_payload(message, gpg_to_cmdline).get_payload() for payload in message.get_payload(): if type(payload.get_payload()) == list: encrypted_payloads.extend(encrypt_all_payloads(payload, gpg_to_cmdline)) else: encrypted_payloads.append(encrypt_payload(payload, gpg_to_cmdline)) return encrypted_payloads
def encrypt_all_payloads_mime(message, gpg_to_cmdline): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param('PGP/MIME version identification', "", 'Content-Description') submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param('name', "encrypted.asc") submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description') submsg2.set_param('inline', "", 'Content-Disposition') submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition') if type(message.get_payload()) == str: # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. # This happens only on text only messages. submsg2.set_payload("\n" + message.get_payload()) check_nested = True else: processed_payloads = generate_message_from_payloads(message) submsg2.set_payload(processed_payloads.as_string()) check_nested = False message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string( ) # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key('Content-Type'): message.replace_header( 'Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary) else: message[ 'Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested)]
def encrypt_all_payloads_mime( message, gpg_to_cmdline ): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param('PGP/MIME version identification', "", 'Content-Description' ) submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param('name', "encrypted.asc") submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description' ) submsg2.set_param('inline', "", 'Content-Disposition' ) submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition' ) if type ( message.get_payload() ) == str: # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. # This happens only on text only messages. submsg2.set_payload("\n" + message.get_payload()) check_nested = True else: processed_payloads = generate_message_from_payloads(message) submsg2.set_payload(processed_payloads.as_string()) check_nested = False message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key('Content-Type'): message.replace_header('Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary) else: message['Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary return [ submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested) ]
def construct_gmail_message(payload): message = email.message.Message() for header in payload['headers']: message[header['name']] = header['value'] if message.get_content_maintype() == 'multipart': message.set_payload( [construct_gmail_message(part) for part in payload['parts']]) else: cte = message.get('Content-Transfer-Encoding') if cte is not None: del message['Content-Transfer-Encoding'] message['X-Original-Content-Transfer-Encoding'] = cte try: external_id = payload['body']['attachmentId'] ct = message.get_content_type() message.replace_header('Content-Type', 'text/plain') message.set_payload( 'Attachment with type %s, ID %r omitted; retrieve separately' % (ct, external_id)) except KeyError: body = payload['body']['data'] body += '=' * (4 - len(body) % 4) message.set_payload(base64.urlsafe_b64decode(body)) return message
def forward(self, email_message, recipient): message = email.message_from_string(email_message) # replace headers (could do other processing here) message.replace_header("From", self.email_address) message.replace_header("To", recipient) message.replace_header("Subject", "Fwd: " + message["subject"]) #header = 'To:' + recipient + '\r\n' + 'From: ' + self.email_address + '\r\n' + 'Subject:' + "Fwd: " + message["subject"] + '\r\n' # open authenticated SMTP connection and send message with # specified envelope from and to addresses smtp = smtplib.SMTP(self.smtp_host, self.smtp_port) smtp.starttls() smtp.login(self.email_address, self.password) #smtp.sendmail(self.email_address, recipient, header + '\r\n' + message.as_string()) smtp.sendmail(self.email_address, recipient, message.as_string()) smtp.quit()
def _process(self, message, rec=0): """ Recursively scan and filter a MIME message. _process will scan the passed message part for invalid headers as well as mailman signatures and modify them according to the global settings. Generic modifications include: * fixing of broken **References:** and **In-Reply-To** headers * generic header filtering (see :meth:`FuCore._filter_headers`) * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`) Args: message: a :class:`email.message` object containing a set of MIME parts rec: Recursion level used to prettify log messages. Returns: A (probably) filtered / modified :class:`email.message` object. """ mm_parts = 0 text_parts = 0 mailman_sig = Reactor.MAILMAN_SIG self._log('>>> processing {0}', message.get_content_type(), rec=rec) if self._conf.complex_footer: self._log('--- using complex mailman filter', rec=rec) mailman_sig = Reactor.MAILMAN_COMPLEX if message.is_multipart(): parts = message._payload else: parts = [ message, ] list_tag = self._find_list_tag(message, rec) reference = message.get('References', None) in_reply = message.get('In-Reply-To', None) x_mailman = message.get('X-Mailman-Version', None) message._headers = self._filter_headers(list_tag, message._headers, self._conf.outlook_hacks, self._conf.fix_dateline, rec) if in_reply and not reference and rec == 0: # set References: to In-Reply-To: if where in toplevel # and References was not set properly self._log('--- set References: {0}', in_reply, rec=rec) try: # uncertain this will ever happen.. message.replace_header('References', in_reply) except KeyError: message._headers.append(('References', in_reply)) for i in xrange(len(parts) - 1, -1, -1): # again, crude since we mutate the list while iterating it.. # the whole reason is the deeply nested structure of email.message p = parts[i] ct = p.get_content_maintype() cs = p.get_content_subtype() ce = p.get('Content-Transfer-Encoding', None) cb = p.get_boundary() self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]', ct, cs, ce, rec=rec) if ct == 'text': text_parts += 1 payload = p.get_payload(decode=True) self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3) if mailman_sig[0].search(payload) and \ mailman_sig[1].match(payload.split('\n')[0]): self._log('*** removing this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) message._payload.remove(p) text_parts -= 1 mm_parts += 1 elif mailman_sig[0].search(payload): self._log('--- trying to mutate..', rec=rec) (use, mutation) = self._mutate_part(payload, rec) if use: self._log('*** mutated this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) payload = mutation mm_parts += 1 # if it was encoded we need to re-encode it # to keep SMIME happy if ce == 'base64': payload = payload.encode('base64') elif ce == 'quoted-printable': payload = quopri.encodestring(payload) p.set_payload(payload) elif ct == 'message' or \ (ct == 'multipart' and cs in ['alternative', 'mixed']): p = self._process(p, rec + 1) else: self._log('--- what about {0}?', p.get_content_type(), rec=rec) if rec == 0: self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]', mm_parts, text_parts, x_mailman, rec=rec) if x_mailman and mm_parts and not text_parts: # if we have # - modified the content # - no text parts left in outer message # - a valid X-Mailmann-Version: # --> remove outer message self._log('!!! beheading this one..', rec=rec) mm = message._payload[0] for h in message._headers: if h[0] == 'Content-Type': continue try: mm.replace_header(h[0], h[1]) except KeyError: mm._headers.append(h) return mm return message
def send_message(request): if request.is_ajax(): # Get the mailbox you want to record this in # Message form values subject = request.POST.get('subject', '') folder_id = request.POST.get('folder_id', '') folder_id = '"{}"'.format(folder_id) message = request.POST.get('message', '').encode("utf-8") message_id = request.POST.get('message_id', '') recipents = request.POST.get('to', '') attachments = request.POST.getlist('attachments[]') cc_recipents = request.POST.get('CC', '') bcc_recipents = request.POST.get('BCC', '') compose = request.POST.get('compose', '') # Split into recipents array recipents = recipents.split(",") cc_recipents = cc_recipents.split(",") or None bcc_recipents = bcc_recipents.split(",") or None # Create a new cnnection conn = UserMailbox.objects.get_mailbox(user=request.user) connection = conn.get_SMTP_connection() matachments = MessageAttachment.objects.filter(pk__in=attachments) # Create an e-mail email_message = EmailMultiAlternatives( subject=subject, body=message, from_email=conn.username, to=recipents, bcc=bcc_recipents, # ['*****@*****.**'], cc=cc_recipents, headers={'Reply-To': request.user.person.email}, connection=connection ) email_message.attach_alternative(message, "text/html") for a in matachments: email_message.attach(a.name, a.document.read()) if conn.imap_server.use_ssl: m = imaplib.IMAP4_SSL(conn.imap_server.host) else: m = imaplib.IMAP4(conn.imap_server.host) m.login(conn.username, conn.password) recipents_str = ','.join(recipents) if compose == "forward": m.select(folder_id) status, data = m.fetch(message_id, "(RFC822)") email_data = data[0][1] m.close() m.logout() # create a Message instance from the email data message = email.message_from_string(email_data) # replace headers (could do other processing here) message.replace_header("From", conn.username) message.replace_header("To", recipents_str) # open authenticated SMTP connection and send message with # specified envelope from and to addresses smtp = smtplib.SMTP_SSL(conn.smtp_server.host, conn.smtp_server.port) smtp.login(conn.username, conn.password) smtp.sendmail(conn.username, recipents_str, message.as_string()) smtp.quit() if compose == "send": email_message.send() # Here we have to use plain imaplib :( # create the message msg = email.message.Message() msg['Subject'] = subject msg['From'] = conn.username msg['To'] = recipents_str msg.set_payload(message) m.append("INBOX.Sent", 'Seen', imaplib.Time2Internaldate(time.time()), str(msg) ) elif compose == "draft": # Here we have to use plain imaplib :( if conn.imap_server.use_ssl: m = imaplib.IMAP4_SSL(conn.imap_server.host) else: m = imaplib.IMAP4(conn.imap_server.host) m.login(conn.username, conn.password) recipents_str = ','.join(recipents) # create the message msg = email.message.Message() msg['Subject'] = subject msg['From'] = conn.username msg['To'] = recipents_str msg.set_payload(message) m.append("INBOX.Drafts", '(\Draft)', imaplib.Time2Internaldate(time.time()), str(msg) ) elif compose == "reply": pass matachments.delete() return HttpResponse('[]', status=200)
def _process(self, message, rec=0): """ Recursively scan and filter a MIME message. _process will scan the passed message part for invalid headers as well as mailman signatures and modify them according to the global settings. Generic modifications include: * fixing of broken **References:** and **In-Reply-To** headers * generic header filtering (see :meth:`FuCore._filter_headers`) * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`) Args: message: a :class:`email.message` object containing a set of MIME parts rec: Recursion level used to prettify log messages. Returns: A (probably) filtered / modified :class:`email.message` object. """ mm_parts = 0 text_parts = 0 mailman_sig = Reactor.MAILMAN_SIG self._log('>>> processing {0}', message.get_content_type(), rec=rec) if self._conf.complex_footer: self._log('--- using complex mailman filter', rec=rec) mailman_sig = Reactor.MAILMAN_COMPLEX if message.is_multipart(): parts = message._payload else: parts = [message,] list_tag = self._find_list_tag(message, rec) reference = message.get('References', None) in_reply = message.get('In-Reply-To', None) x_mailman = message.get('X-Mailman-Version', None) message._headers = self._filter_headers(list_tag, message._headers, self._conf.outlook_hacks, self._conf.fix_dateline, rec) if in_reply and not reference and rec == 0: # set References: to In-Reply-To: if where in toplevel # and References was not set properly self._log('--- set References: {0}', in_reply, rec=rec) try: # uncertain this will ever happen.. message.replace_header('References', in_reply) except KeyError: message._headers.append(('References', in_reply)) for i in xrange(len(parts) - 1, -1, -1): # again, crude since we mutate the list while iterating it.. # the whole reason is the deeply nested structure of email.message p = parts[i] ct = p.get_content_maintype() cs = p.get_content_subtype() ce = p.get('Content-Transfer-Encoding', None) cb = p.get_boundary() self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]', ct, cs, ce, rec=rec) if ct == 'text': text_parts += 1 payload = p.get_payload(decode=True) self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3) if mailman_sig[0].search(payload) and \ mailman_sig[1].match(payload.split('\n')[0]): self._log('*** removing this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) message._payload.remove(p) text_parts -= 1 mm_parts += 1 elif mailman_sig[0].search(payload): self._log('--- trying to mutate..', rec=rec) (use, mutation) = self._mutate_part(payload, rec) if use: self._log('*** mutated this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) payload = mutation mm_parts += 1 # if it was encoded we need to re-encode it # to keep SMIME happy if ce == 'base64': payload = payload.encode('base64') elif ce == 'quoted-printable': payload = quopri.encodestring(payload) p.set_payload(payload) elif ct == 'message' or \ (ct == 'multipart' and cs in ['alternative', 'mixed']): p = self._process(p, rec + 1) else: self._log('--- what about {0}?', p.get_content_type(), rec=rec) if rec == 0: self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]', mm_parts, text_parts, x_mailman, rec=rec) if x_mailman and mm_parts and not text_parts: # if we have # - modified the content # - no text parts left in outer message # - a valid X-Mailmann-Version: # --> remove outer message self._log('!!! beheading this one..', rec=rec) mm = message._payload[0] for h in message._headers: if h[0] == 'Content-Type': continue try: mm.replace_header(h[0], h[1]) except KeyError: mm._headers.append(h) return mm return message
def _apply_blacklist(self, message, mode, rec=0): """ Apply the global blacklist to this message. This method will modify the headers of the message or decide to discard the whole message based on the global blacklist. :param messag: A :class:`email.message` object. :param mode: One of :attr:`FUCore.BLACKLIST_MODES` :returns: The modified message or None if the message should be dropped. """ if not mode.lower() in FUCore.BLACKLIST_MODES: self._log('!!! Invalid blacklist mode "{0}", not modifying message.', mode, rec=rec) return message mfrom = message.get('From', '').split('<')[-1].split('>')[0] sender = message.get('Sender', '').split('<')[-1].split('>')[0] if not mfrom and not msender: self._log('!!! Message has neiter "From:" nor "Sender:" headers!', rec=rec) return message list_entry = self._blacklist.get(mfrom.lower(), self._blacklist.get(sender.lower(), None)) if not list_entry: return message self._log('--- applying blacklist rule {0} to message', str(list_entry), rec=rec) if not list_entry['action'].lower() in ['d', 'n', 'e', 'ne', 'en']: # invalid self._log('!!! unsupported blacklist rule "{0}"', list_entry['action'], rec=rec) return message add_expires=False add_xnay=False if list_entry['action'].lower() == 'd' and not mode.lower() == 'reactor': self._log('--- blacklist rule "drop"', rec=rec, verbosity=2) return None elif mode.lower() == 'news2mail': #D=N=E=NE/EN=drop self._log('--- blacklist rule D=N=E=NE/EN "drop"', rec=rec, verbosity=2) return None elif mode.lower() == 'mail2news': if list_entry['action'].lower() in ['ne', 'en','n']: add_xnay = True if list_entry['action'].lower() in ['ne', 'en', 'e']: add_expires = True elif mode.lower() == 'reactor': if list_entry['action'].lower() in ['ne','en','n','d']: add_xnay = True if list_entry['action'].lower() in ['ne','en','e']: add_expires = True if add_xnay: self._log('--- blacklist rule "xnay"', rec=rec, verbosity=2) try: message.replace_header('X-No-Archive', 'yes') except KeyError: message._headers.append(('X-No-Archive', 'yes')) if add_expires: if not list_entry.get('param', None): self._log('!!! blacklist rule "expires" missing a parameter', rec=rec) else: try: delay=long(list_entry['param']) expires=time.strftime(r"%d %b %Y %H:%M:%S %z", time.localtime(time.time() + (86400*delay))) self._log('--- blacklist rule "expires" => {0}', expires, rec=rec, verbosity=2) try: message.replace_header('Expires', expires) except KeyError: message._headers.append(('Expires', expires)) except ValueError: self.log('!!! blacklist rule "expires" needs a *numeric* parameter.') return message
def _apply_blacklist(self, message, mode, rec=0): """ Apply the global blacklist to this message. This method will modify the headers of the message or decide to discard the whole message based on the global blacklist. :param messag: A :class:`email.message` object. :param mode: One of :attr:`FUCore.BLACKLIST_MODES` :returns: The modified message or None if the message should be dropped. """ if not mode.lower() in FUCore.BLACKLIST_MODES: self._log( '!!! Invalid blacklist mode "{0}", not modifying message.', mode, rec=rec) return message mfrom = message.get('From', '').split('<')[-1].split('>')[0] sender = message.get('Sender', '').split('<')[-1].split('>')[0] if not mfrom and not msender: self._log('!!! Message has neiter "From:" nor "Sender:" headers!', rec=rec) return message list_entry = self._blacklist.get( mfrom.lower(), self._blacklist.get(sender.lower(), None)) if not list_entry: return message self._log('--- applying blacklist rule {0} to message', str(list_entry), rec=rec) if not list_entry['action'].lower() in ['d', 'n', 'e', 'ne', 'en']: # invalid self._log('!!! unsupported blacklist rule "{0}"', list_entry['action'], rec=rec) return message add_expires = False add_xnay = False if list_entry['action'].lower( ) == 'd' and not mode.lower() == 'reactor': self._log('--- blacklist rule "drop"', rec=rec, verbosity=2) return None elif mode.lower() == 'news2mail': #D=N=E=NE/EN=drop self._log('--- blacklist rule D=N=E=NE/EN "drop"', rec=rec, verbosity=2) return None elif mode.lower() == 'mail2news': if list_entry['action'].lower() in ['ne', 'en', 'n']: add_xnay = True if list_entry['action'].lower() in ['ne', 'en', 'e']: add_expires = True elif mode.lower() == 'reactor': if list_entry['action'].lower() in ['ne', 'en', 'n', 'd']: add_xnay = True if list_entry['action'].lower() in ['ne', 'en', 'e']: add_expires = True if add_xnay: self._log('--- blacklist rule "xnay"', rec=rec, verbosity=2) try: message.replace_header('X-No-Archive', 'yes') except KeyError: message._headers.append(('X-No-Archive', 'yes')) if add_expires: if not list_entry.get('param', None): self._log('!!! blacklist rule "expires" missing a parameter', rec=rec) else: try: delay = long(list_entry['param']) expires = time.strftime( r"%d %b %Y %H:%M:%S %z", time.localtime(time.time() + (86400 * delay))) self._log('--- blacklist rule "expires" => {0}', expires, rec=rec, verbosity=2) try: message.replace_header('Expires', expires) except KeyError: message._headers.append(('Expires', expires)) except ValueError: self.log( '!!! blacklist rule "expires" needs a *numeric* parameter.' ) return message