def send(msg, smtpurl, port, keyfile, certfile, username, password): srcaddr = getaddresses(msg.get_all('From',[])) tos = msg.get_all('to',[]) ccs = msg.get_all('cc',[]) bccs = msg.get_all('bcc',[]) resent_tos = msg.get_all('resent-to',[]) resent_ccs = msg.get_all('resent-cc',[]) toaddrlist = getaddresses(tos + ccs + bccs + resent_tos + resent_ccs) destaddr = [] for (name, addr) in toaddrlist: if not (addr in destaddr): destaddr.append(addr) del msg['Bcc'] client = smtplib.SMTP() #client.set_debuglevel(2) client.connect(smtpurl, port) client.ehlo() if keyfile and certfile: client.starttls(keyfile, certfile) else: client.starttls() client.ehlo() if username and password: client.login(username, password) client.sendmail(srcaddr[0][1], destaddr, rstrip(msg.as_string())) client.quit()
def getAttachements(rfcmessage): container = [] message = email.message_from_string(rfcmessage) subject = decode_subject(message["subject"]) tos = message.get_all('to', []) ccs = message.get_all('cc', []) resent_tos = message.get_all('resent-to', []) resent_ccs = message.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) froms = message.get_all('from', []) try: message_id = message["message-id"] except: message_id = "not_unique" sender = getaddresses(froms) recepients = [] for to_whom_name, to_whom in all_recipients: try: recepients.append(USER_TO_URL_RE.match(to_whom).groupdict()) except: pass counter = 0 for msg in message.walk(): if not msg.is_multipart() and msg.get_content_maintype() != 'message': ct = msg.get_content_type() mtype = msg.get_content_maintype().lower() payload = msg.get_payload() te = msg.get('Content-Transfer-Encoding', '8bit').lower() if te == 'base64': try: payload = email.base64MIME.decode(payload) except: payload = '' # ??? elif te == 'quoted-printable': try: payload = email.quopriMIME.decode(payload) except: payload = '' # ??? if mtype == 'text' or mtype is None: try: tc = msg.get_content_charset() except: tc = 'latin-1' else: pass if mtype in ('image', 'video', 'audio', 'text', 'application'): counter += 1 name = dict(msg.get_params() or []).get('name', 'noname-%i.txt' % counter) container.append((ct, name, payload)) else: pass return Storage(sender=sender, recepients=recepients, subject=subject, message_id=message_id, container=container,)
def secureSend(self, message, mto, mfrom, subject='[No Subject]', mcc=None, mbcc=None, subtype='plain', charset='us-ascii', debug=False, **kwargs): """Deprecated method attempting to maintain backwards compatibility for code depending on the SecureMailHost API.""" # Convert all our address list inputs mfrom = email_list_to_string(mfrom, charset) mto = email_list_to_string(mto, charset) mcc = email_list_to_string(mcc, charset) mbcc = email_list_to_string(mbcc, charset) # Convert to a message for adding headers. If it's already a # message, copy it to be safe. if not isinstance(message, Message): if isinstance(message, unicode): message.encode(charset) message = MIMEText(message, subtype, charset) else: message = deepcopy(message) # Add extra headers _addHeaders(message, Subject=Header(subject, charset), To=mto, Cc=mcc, From=mfrom, **dict((k, Header(v, charset)) for k, v in kwargs.iteritems())) all_recipients = [formataddr(pair) for pair in getaddresses((mto, mcc, mbcc))] # Convert message back to string for sending self._send(mfrom, all_recipients, message.as_string(), immediate=True)
def main(): fileconf = os.path.expanduser("~/.mysender.conf") conf = ConfigParser.ConfigParser() conf.readfp(open(fileconf)) msg = email.message_from_file(sys.stdin) srcaddr = getaddresses(msg.get_all("From", [])) (user, account) = split(srcaddr[0][1], "@") host = conf.get(account, "host") port = conf.getint(account, "port") keyfile = str() if conf.has_option(account, "keyfile"): keyfile = conf.get(account, "keyfile") certfile = str() if conf.has_option(account, "certfile"): certfile = conf.get(account, "certfile") username = str() if conf.has_option(account, "username"): username = conf.get(account, "username") password = str() if conf.has_option(account, "password"): password = conf.get(account, "password") send(msg, host, port, keyfile, certfile, username, password)
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET val = force_unicode(val) if '\n' in val or '\r' in val: raise BadHeaderError( "Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode('ascii') except UnicodeEncodeError: if name.lower() in ('to', 'from', 'cc'): result = [] for nm, addr in getaddresses((val, )): nm = str(Header(nm.encode(encoding), encoding)) try: addr = addr.encode('ascii') except UnicodeEncodeError: # IDN addr = str(Header(addr.encode(encoding), encoding)) result.append(formataddr((nm, addr))) val = ', '.join(result) else: val = Header(val.encode(encoding), encoding) else: if name.lower() == 'subject': val = Header(val) return name, val
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET val = force_unicode(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode('ascii') except UnicodeEncodeError: if name.lower() in ('to', 'from', 'cc'): result = [] for nm, addr in getaddresses((val,)): nm = str(Header(nm.encode(encoding), encoding)) try: addr = addr.encode('ascii') except UnicodeEncodeError: # IDN addr = str(Header(addr.encode(encoding), encoding)) result.append(formataddr((nm, addr))) val = ', '.join(result) else: val = Header(val.encode(encoding), encoding) else: if name.lower() == 'subject': val = Header(val) return name, val
def check_to_cc(self, arguments): assert not arguments, arguments from email.Utils import getaddresses count = len(getaddresses(self.message.get_all('To', []) + self.message.get_all('Cc', []))) if count > 50: self.checker.reject("More than 50 recipients (%d)." % count)
def main(): fileconf = os.path.expanduser('~/.mysender.conf') conf = ConfigParser.ConfigParser() conf.readfp(open(fileconf)) msg = email.message_from_file(sys.stdin) srcaddr = getaddresses(msg.get_all('From',[])) (user, account) = split(srcaddr[0][1],'@') host = conf.get(account, 'host') port = conf.getint(account, 'port') keyfile = str() if conf.has_option(account, 'keyfile'): keyfile = conf.get(account, 'keyfile') certfile = str() if conf.has_option(account, 'certfile'): certfile = conf.get(account, 'certfile') username = str() if conf.has_option(account, 'username'): username = conf.get(account, 'username') password = str() if conf.has_option(account, 'password'): password = conf.get(account, 'password') send(msg, host, port, keyfile, certfile, username, password)
def determine_sender(mail, action='reply'): """ Inspect a given mail to reply/forward/bounce and find the most appropriate account to act from and construct a suitable From-Header to use. :param mail: the email to inspect :type mail: `email.message.Message` :param action: intended use case: one of "reply", "forward" or "bounce" :type action: str """ assert action in ['reply', 'forward', 'bounce'] realname = None address = None # get accounts my_accounts = settings.get_accounts() assert my_accounts, 'no accounts set!' # extract list of addresses to check for my address # X-Envelope-To and Envelope-To are used to store the recipient address # if not included in other fields candidate_addresses = getaddresses(mail.get_all('To', []) + mail.get_all('Cc', []) + mail.get_all('Delivered-To', []) + mail.get_all('X-Envelope-To', []) + mail.get_all('Envelope-To', []) + mail.get_all('From', [])) logging.debug('candidate addresses: %s' % candidate_addresses) # pick the most important account that has an address in candidates # and use that accounts realname and the address found here for account in my_accounts: acc_addresses = account.get_addresses() for alias in acc_addresses: if realname is not None: break regex = re.compile(re.escape(alias), flags=re.IGNORECASE) for seen_name, seen_address in candidate_addresses: if regex.match(seen_address): logging.debug("match!: '%s' '%s'" % (seen_address, alias)) if settings.get(action + '_force_realname'): realname = account.realname else: realname = seen_name if settings.get(action + '_force_address'): address = account.address else: address = seen_address # revert to default account if nothing found if realname is None: account = my_accounts[0] realname = account.realname address = account.address logging.debug('using realname: "%s"' % realname) logging.debug('using address: %s' % address) from_value = address if realname == '' else '%s <%s>' % (realname, address) return from_value, account
def getCC(self): res = [] buf = getaddresses(self._msg.get_all('cc', '')) for i in buf: header = decode_header(i[0]) data = ''.join([to_unicode(s, enc) for s, enc in header if self.codecs_lookup(enc)]) res.append((to_entities_quote(data), i[1])) return res
def determine_sender(mail, action='reply'): """ Inspect a given mail to reply/forward/bounce and find the most appropriate account to act from and construct a suitable From-Header to use. :param mail: the email to inspect :type mail: `email.message.Message` :param action: intended use case: one of "reply", "forward" or "bounce" :type action: str """ assert action in ['reply', 'forward', 'bounce'] realname = None address = None # get accounts my_accounts = settings.get_accounts() assert my_accounts, 'no accounts set!' # extract list of addresses to check for my address # X-Envelope-To and Envelope-To are used to store the recipient address # if not included in other fields candidate_addresses = getaddresses( mail.get_all('To', []) + mail.get_all('Cc', []) + mail.get_all('Delivered-To', []) + mail.get_all('X-Envelope-To', []) + mail.get_all('Envelope-To', []) + mail.get_all('From', [])) logging.debug('candidate addresses: %s' % candidate_addresses) # pick the most important account that has an address in candidates # and use that accounts realname and the address found here for account in my_accounts: acc_addresses = account.get_addresses() for alias in acc_addresses: if realname is not None: break regex = re.compile(re.escape(alias), flags=re.IGNORECASE) for seen_name, seen_address in candidate_addresses: if regex.match(seen_address): logging.debug("match!: '%s' '%s'" % (seen_address, alias)) if settings.get(action + '_force_realname'): realname = account.realname else: realname = seen_name if settings.get(action + '_force_address'): address = account.address else: address = seen_address # revert to default account if nothing found if realname is None: account = my_accounts[0] realname = account.realname address = account.address logging.debug('using realname: "%s"' % realname) logging.debug('using address: %s' % address) from_value = address if realname == '' else '%s <%s>' % (realname, address) return from_value, account
def send_smtp(msg, bcc=None): ''' Send a Message via SMTP, based on the django email server settings. The destination list will be taken from the To:/Cc: headers in the Message. The From address will be used if present or will default to the django setting DEFAULT_FROM_EMAIL If someone has set test_mode=True, then append the msg to the outbox. ''' add_headers(msg) (fname, frm) = parseaddr(msg.get('From')) addrlist = msg.get_all('To') + msg.get_all('Cc', []) if bcc: addrlist += [bcc] to = [addr for name, addr in getaddresses(addrlist) if ( addr != '' and not addr.startswith('unknown-email-') )] if not to: log("No addressees for email from '%s', subject '%s'. Nothing sent." % (frm, msg.get('Subject', '[no subject]'))) else: if test_mode: outbox.append(msg) server = None try: server = smtplib.SMTP() #log("SMTP server: %s" % repr(server)) #if settings.DEBUG: # server.set_debuglevel(1) conn_code, conn_msg = server.connect(SMTP_ADDR['ip4'], SMTP_ADDR['port']) #log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg)) if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD: server.ehlo() if 'starttls' not in server.esmtp_features: raise ImproperlyConfigured('password configured but starttls not supported') (retval, retmsg) = server.starttls() if retval != 220: raise ImproperlyConfigured('password configured but tls failed: %d %s' % ( retval, retmsg )) # Send a new EHLO, since without TLS the server might not # advertise the AUTH capability. server.ehlo() server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD) unhandled = server.sendmail(frm, to, msg.as_string()) if unhandled != {}: raise SMTPSomeRefusedRecipients(message="%d addresses were refused"%len(unhandled),original_msg=msg,refusals=unhandled) except Exception as e: # need to improve log message log("Exception while trying to send email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]'))) if isinstance(e, smtplib.SMTPException): e.original_msg=msg raise else: raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': traceback.format_tb(sys.exc_info()[2])}) finally: try: server.quit() except smtplib.SMTPServerDisconnected: pass log("sent email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]')))
def clear_my_address(self, my_addresses, value): """return recipient header without the addresses in my_addresses""" new_value = [] for name, address in getaddresses(value): if address not in my_addresses: if name != '': new_value.append('"%s" <%s>' % (name, address)) else: new_value.append(address) return ', '.join(new_value)
def __init__(self, context, message): # -> none """Extract the bits of interest from an RFC2822 message string. context should be a wiki page. This perhaps should do the isJunk test up front to avoid unnecessary resource usage. """ DEBUG('mailin.py processing incoming message:\n%s' % message) self.context = context self.original = message self.msg = email.message_from_string(self.original) self.date = self.msg['Date'] # flatten a multi-line subject into one line s = re.sub('\n', '', self.msg.get('Subject', '')) # convert the possibly RFC2047-encoded subject to unicode. # Only the first encoded part is used if there is more than one. # misencoded subjects are ignored. (s, enc) = decode_header(s)[0] try: self.subject = tounicode(s, enc or 'ascii') except UnicodeDecodeError: self.subject = '' self.realSubject = re.sub(r'.*?\[.*?\] ?(.*)', r'\1', self.subject) self.messageid = self.msg.get('Message-id', '') self.inreplyto = self.msg.get('In-reply-to', '') self.From = self.msg.get('From') self.FromRealName = parseaddr(self.From)[0] self.FromEmail = parseaddr(self.From)[1] self.FromUserName = (self.FromRealName or re.sub(r'@.*$', r'', self.FromEmail)) self.sender = self.msg.get('Sender') self.senderEmail = (self.sender and parseaddr(self.sender)[1]) or None tos = self.msg.get_all('to', []) ccs = self.msg.get_all('cc', []) resent_tos = self.msg.get_all('resent-to', []) resent_ccs = self.msg.get_all('resent-cc', []) self.recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) # mailing list support # XXX x-beenthere is mailman-specific - need to support ezmlm & others here #self.xbeenthere = (self.msg.get('X-BeenThere') or # re.search(r'[^\s<]+@[^\s>]+',self.msg.get('Delivered-To')).group()) # ..Type Error - configured ezmlm to provide beenthere instead (?) self.xbeenthere = self.msg.get('X-BeenThere') # the mailin body will be the message's first text/plain part # (or a null string if there is none or it's misencoded) try: firstplaintextpart = typed_subpart_iterator( self.msg, 'text', 'plain').next() # as I understand it: # first decoding, from the content-transfer-encoding, eg quoted-printabe payload = firstplaintextpart.get_payload(decode=1) # second decoding, from utf8 or whatever to unicode charset = self.msg.get_content_charset('ascii') payloadutf8 = payload.decode(charset).encode('utf-8') except (StopIteration, UnicodeDecodeError): payloadutf8 = '' self.body = cleanupBody(payloadutf8)
def send_smtp(msg, bcc=None): ''' Send a Message via SMTP, based on the django email server settings. The destination list will be taken from the To:/Cc: headers in the Message. The From address will be used if present or will default to the django setting DEFAULT_FROM_EMAIL If someone has set test_mode=True, then just append the msg to the outbox. ''' add_headers(msg) (fname, frm) = parseaddr(msg.get('From')) addrlist = msg.get_all('To') + msg.get_all('Cc', []) if bcc: addrlist += [bcc] to = [addr for name, addr in getaddresses(addrlist)] if not to: log("No addressees for email from '%s', subject '%s'. Nothing sent." % (frm, msg.get('Subject', '[no subject]'))) else: if test_mode: outbox.append(msg) return server = None try: server = smtplib.SMTP() log("SMTP server: %s" % repr(server)) #if settings.DEBUG: # server.set_debuglevel(1) conn_code, conn_msg = server.connect(settings.EMAIL_HOST, settings.EMAIL_PORT) log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg)) if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD: server.ehlo() if 'starttls' not in server.esmtp_features: raise ImproperlyConfigured('password configured but starttls not supported') (retval, retmsg) = server.starttls() if retval != 220: raise ImproperlyConfigured('password configured but tls failed: %d %s' % ( retval, retmsg )) # Send a new EHLO, since without TLS the server might not # advertise the AUTH capability. server.ehlo() server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD) server.sendmail(frm, to, msg.as_string()) # note: should pay attention to the return code, as it may # indicate that someone didn't get the email. except: if server: server.quit() # need to improve log message log("got exception '%s' (%s) trying to send email from '%s' to %s subject '%s'" % (sys.exc_info()[0], sys.exc_info()[1], frm, to, msg.get('Subject', '[no subject]'))) if isinstance(sys.exc_info()[0], smtplib.SMTPException): raise else: raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': sys.exc_info()[2]}) server.quit() log("sent email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]')))
def source_email(msg, return_realname=False): """ Search the header of an email Message instance to find the sender's email address. """ froms = msg.get_all('from', []) from_tuples = getaddresses(froms) # [(realname, email_address), ...] assert len(from_tuples) == 1 if return_realname == True: return from_tuples[0] # (realname, email_address) return from_tuples[0][1] # email_address
def validateSingleEmailAddress(self, address): # Validate a single email address, see also validateEmailAddresses. if not isinstance(address, basestring): return False sub = EMAIL_CUTOFF_RE.match(address) if sub is not None: # Address contains two newlines (spammer attack using # "address\n\nSpam message") return False if len(getaddresses([address])) != 1: # none or more than one address return False # Validate the address for name, addr in getaddresses([address]): if not self.validateSingleNormalizedEmailAddress(addr): return False return True
def __init__(self, message, hash_value, uid): self.hash_value = hash_value self.uid = uid message = email.message_from_string(message) def _get_header_data(key): value = message.get(key, '<unknown>') value = decode_mail_headers(decodeUnknown(message.get_charset(), value)) return value self.subject = _get_header_data('subject').strip() self.sender = parseaddr(message.get('from')) received = None date_str= message.get('date') if date_str: date_tuple= parsedate_tz(date_str) if date_tuple: received=datetime.datetime.fromtimestamp(mktime_tz(date_tuple)) self.received = received self.recipients = getaddresses(message.get_all('to', []) + message.get_all('cc', []) + message.get_all('resent-to', []) + message.get_all('resent-cc', [])) counter = 0 files = [] body_plain, body_html = '', '' for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name == None: if part.get_content_subtype() == 'plain': body_plain = decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) else: body_html = part.get_payload(decode=True) else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append({ 'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()}, ) counter += 1 self.body_plain = body_plain self.body_html = mark_safe(strip_empty_tags(strip_tags(body_html, ['html', 'head', 'body', 'meta']))) self.files = files
def getCC(self): res = [] buf = getaddresses(self._msg.get_all('cc', '')) for i in buf: header = decode_header(i[0]) data = ''.join([ to_unicode(s, enc) for s, enc in header if self.codecs_lookup(enc) ]) res.append((to_entities_quote(data), i[1])) return res
def send(msg, smtpurl, port, keyfile, certfile, username, password): srcaddr = getaddresses(msg.get_all('From',[])) tos = msg.get_all('to',[]) ccs = msg.get_all('cc',[]) bccs = msg.get_all('bcc',[]) resent_tos = msg.get_all('resent-to',[]) resent_ccs = msg.get_all('resent-cc',[]) toaddrlist = getaddresses(tos + ccs + bccs + resent_tos + resent_ccs) destaddr = [] for (name, addr) in toaddrlist: if not (addr in destaddr): destaddr.append(addr) del msg['Bcc'] client = smtplib.SMTP() client.set_debuglevel(2) client.connect(smtpurl, port) client.ehlo() if keyfile and certfile: client.starttls(keyfile, certfile) else: client.starttls() client.ehlo() if username and password: client.login(username, password) client.sendmail(srcaddr[0][1], destaddr, rstrip(msg.as_string())) client.quit() out = open('/home/denever/mail/logs/sender.log', "a") log = "\nId: "+msg['message-id'] log += "\tTo:"+join(destaddr) log += "\tDate: "+msg['date'] log += "\tSub: "+msg['subject'] log += "\nUsing: " + smtpurl out.write(log) out.close()
def target_emails(msg): """ Search the header of an email Message instance to find a list of recipient's email addresses. """ tos = msg.get_all('to', []) ccs = msg.get_all('cc', []) bccs = msg.get_all('bcc', []) resent_tos = msg.get_all('resent-to', []) resent_ccs = msg.get_all('resent-cc', []) resent_bccs = msg.get_all('resent-bcc', []) all_recipients = getaddresses(tos + ccs + bccs + resent_tos + resent_ccs + resent_bccs) return [addr[1] for addr in all_recipients]
def validateSingleEmailAddress(self, address): """ Validate a single email address, see also validateEmailAddresses. """ if not isinstance(address, basestring): return False sub = EMAIL_CUTOFF_RE.match(address) if sub != None: # Address contains two newlines (spammer attack using # "address\n\nSpam message") return False email = getaddresses([address]) # #5353 check if the tuple has any entries for the address, with a bad # email adress it returned a list with this format [("", "")] # first entry should have been full name and second if email is correct if len(email) != 1 or email[0][1] == "": # none or more than one address return False # Validate the address for _, addr in getaddresses([address]): if not self.validateSingleNormalizedEmailAddress(addr): return False return True
def __init__(self, context, message, ): """ Extract the bits of interest from an RFC2822 message string. This perhaps should do the isJunk test up front to avoid unnecessary resource usage. """ BLATHER('mailin.py processing incoming message:\n%s' % message) #BLATHER('mailin.py processing incoming message') self.context = context self.original = message self.msg = email.message_from_string(self.original) self.date = self.msg['Date'] self.subject = re.sub(r'\n',r'',self.msg.get('Subject','')) self.realSubject = re.sub(r'.*?\[.*?\] ?(.*)',r'\1',self.subject) self.messageid = self.msg.get('Message-id','') self.inreplyto = self.msg.get('In-reply-to','') self.From = self.msg.get('From') self.FromRealName = parseaddr(self.From)[0] self.FromEmail = parseaddr(self.From)[1] self.FromUserName = (self.FromRealName or re.sub(r'@.*$',r'',self.FromEmail)) self.sender = self.msg.get('Sender') self.senderEmail = (self.sender and parseaddr(self.sender)[1]) or None tos = self.msg.get_all('to', []) ccs = self.msg.get_all('cc', []) resent_tos = self.msg.get_all('resent-to', []) resent_ccs = self.msg.get_all('resent-cc', []) self.recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) # mailing list support # XXX x-beenthere is mailman-specific - need to support ezmlm & others here #self.xbeenthere = (self.msg.get('X-BeenThere') or # re.search(r'[^\s<]+@[^\s>]+',self.msg.get('Delivered-To')).group()) # ..Type Error - configured ezmlm to provide beenthere instead (?) self.xbeenthere = self.msg.get('X-BeenThere') # raises an exception if there's no text part try: plaintextpart = typed_subpart_iterator(self.msg, 'text', 'plain').next().get_payload(decode=1) except StopIteration: plaintextpart = '' self.body = self.cleanupBody(plaintextpart)
def forbid_multi_line_headers(name, val, encoding): encoding = encoding or DEFAULT_CHARSET val = force_unicode(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode('ascii') except UnicodeEncodeError: if name.lower() in ('to', 'from', 'cc'): val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: val = str(Header(val, encoding)) else: if name.lower() == 'subject': val = Header(val) return name, val
def forbid_multi_line_headers(name, val, encoding): encoding = encoding or DEFAULT_CHARSET val = force_unicode(val) if "\n" in val or "\r" in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode("ascii") except UnicodeEncodeError: if name.lower() in ("to", "from", "cc"): val = ", ".join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: val = str(Header(val, encoding)) else: if name.lower() == "subject": val = Header(val) return name, val
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET val = force_unicode(val) if "\n" in val or "\r" in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode("ascii") except UnicodeEncodeError: if name.lower() in ADDRESS_HEADERS: val = ", ".join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: val = str(Header(val, encoding)) else: if name.lower() == "subject": val = Header(val) return name, val
def validateEmailAddresses(self, addresses): # Validate a list of possibly several email addresses, see also # validateSingleEmailAddress. if not isinstance(addresses, basestring): return False sub = EMAIL_CUTOFF_RE.match(addresses) if sub is not None: # Addresses contains two newlines (spammer attack using # "To: list\n\nSpam message") return False # Validate each address for name, addr in getaddresses([addresses]): if not self.validateSingleNormalizedEmailAddress(addr): return False return True
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or "utf8" val = unicode(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode('ascii') except UnicodeEncodeError: if name.lower() in ADDRESS_HEADERS: val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: val = str(Header(val, encoding)) else: if name.lower() == 'subject': val = Header(val) return name, val
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET val = force_unicode(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode('ascii') except UnicodeEncodeError: if name.lower() in ADDRESS_HEADERS: val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: val = str(Header(val, encoding)) else: if name.lower() == 'subject': val = Header(val) return name, val
def process(msg): if msg.is_multipart(): return None try: whofrom = getaddresses([msg.get('from', '')])[0][1] if not whofrom: return None username, domain = whofrom.split('@', 1) except (IndexError, ValueError): return None if username.lower() <> 'mailer-daemon': return None parts = domain.split('.') parts.reverse() for part1, part2 in zip(parts, ('edu', 'yale')): if part1 <> part2: return None # Okay, we've established that the bounce came from the mailer-daemon at # yale.edu. Let's look for a name, and then guess the relevant domains. names = {} body = StringIO(msg.get_payload()) state = 0 # simple state machine # 0 == init # 1 == intro found while 1: line = body.readline() if not line: break if state == 0 and scre.search(line): state = 1 elif state == 1 and ecre.search(line): break elif state == 1: mo = acre.search(line) if mo: names[mo.group('addr')] = 1 # Now we have a bunch of names, these are either @yale.edu or # @cs.yale.edu. Add them both. addrs = [] for name in names.keys(): addrs.append(name + '@yale.edu') addrs.append(name + '@cs.yale.edu') return addrs
def get_addresses_from_header(email_header): r"""Get the e-mail addresses specificed in an e-mail header. >>> get_addresses_from_header('*****@*****.**') ['*****@*****.**'] >>> get_addresses_from_header('[email protected], [email protected]') ['*****@*****.**', '*****@*****.**'] >>> get_addresses_from_header('One\n <*****@*****.**>') ['One <*****@*****.**>'] >>> get_addresses_from_header('One\r\n <*****@*****.**>') ['One <*****@*****.**>'] >>> get_addresses_from_header( ... '"One, A" <*****@*****.**>,\n' ... ' "Two, B" <*****@*****.**>') ['"One, A" <*****@*****.**>', '"Two, B" <*****@*****.**>'] """ return [ formataddr((name, address)) for name, address in getaddresses([email_header])]
def send_message(request, message, recipient_filter=lambda x: True): sender = message["from"] recipients = set() for field in ["to", "cc", "bcc"]: values = message.get_all(field, []) for _, address in getaddresses(values): if recipient_filter(address): recipients.add(address) smtp = smtplib.SMTP() try: try: smtp.connect(request.cfg.mail_smarthost) smtp.ehlo() try: smtp.starttls() except smtplib.SMTPException: pass else: smtp.ehlo() try: smtp.sendmail(sender, recipients, message.as_string()) for recipient in recipients: logging.info( "%s invited %s to wiki %s" % (sender, recipients, request.cfg.interwikiname)) except (smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused), error: if not getattr(request.cfg, "mail_login", None): raise error smtp.login(*request.cfg.mail_login.split(" ", 1)) smtp.sendmail(sender, recipients, message.as_string()) except Exception, exc: raise InviteException("Could not send the mail: %r" % exc) finally: try: smtp.quit() except Exception: pass
def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET val = force_unicode(val) if "\n" in val or "\r" in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: val = val.encode("ascii") except UnicodeEncodeError: if name.lower() in ("to", "from", "cc"): result = [] for nm, addr in getaddresses((val,)): nm = str(Header(nm.encode(encoding), encoding)) result.append(formataddr((nm, str(addr)))) val = ", ".join(result) else: val = Header(val.encode(encoding), encoding) else: if name.lower() == "subject": val = Header(val) return name, val
def get_addresses_from_header(email_header): r"""Get the e-mail addresses specificed in an e-mail header. >>> get_addresses_from_header('*****@*****.**') ['*****@*****.**'] >>> get_addresses_from_header('[email protected], [email protected]') ['*****@*****.**', '*****@*****.**'] >>> get_addresses_from_header('One\n <*****@*****.**>') ['One <*****@*****.**>'] >>> get_addresses_from_header('One\r\n <*****@*****.**>') ['One <*****@*****.**>'] >>> get_addresses_from_header( ... '"One, A" <*****@*****.**>,\n' ... ' "Two, B" <*****@*****.**>') ['"One, A" <*****@*****.**>', '"Two, B" <*****@*****.**>'] """ return [ formataddr((name, address)) for name, address in getaddresses([email_header]) ]
def condition_message(to, frm, subject, msg, cc, extra): if isinstance(frm, tuple): frm = formataddr(frm) if isinstance(to, list) or isinstance(to, tuple): to = ", ".join([isinstance(addr, tuple) and formataddr(addr) or addr for addr in to if addr]) if isinstance(cc, list) or isinstance(cc, tuple): cc = ", ".join([isinstance(addr, tuple) and formataddr(addr) or addr for addr in cc if addr]) if frm: msg['From'] = frm # The following is a hack to avoid an issue with how the email module (as of version 4.0.3) # breaks lines when encoding header fields with anything other than the us-ascii codec. # This allows the Header implementation to encode each display name as a separate chunk. # The resulting encode produces a string that is us-ascii and has a good density of # "higher-level syntactic breaks" to_hdr = Header(header_name='To') for name, addr in getaddresses([to]): if addr != '' and not addr.startswith('unknown-email-'): if name: to_hdr.append('"%s"' % name) to_hdr.append("<%s>," % addr) to_str = to_hdr.encode() if to_str and to_str[-1] == ',': to_str=to_str[:-1] # It's important to use this string, and not assign the Header object. # Code downstream from this assumes that the msg['To'] will return a string, not an instance msg['To'] = to_str if cc: msg['Cc'] = cc msg['Subject'] = subject msg['X-Test-IDTracker'] = (settings.SERVER_MODE == 'production') and 'no' or 'yes' msg['X-IETF-IDTracker'] = ietf.__version__ msg['Auto-Submitted'] = "auto-generated" msg['Precedence'] = "bulk" if extra: for k, v in extra.items(): if v: msg[k] = v
def send_message(request, message, recipient_filter=lambda x: True): sender = message["from"] recipients = set() for field in ["to", "cc", "bcc"]: values = message.get_all(field, []) for _, address in getaddresses(values): if recipient_filter(address): recipients.add(address) smtp = smtplib.SMTP() try: try: smtp.connect(request.cfg.mail_smarthost) smtp.ehlo() try: smtp.starttls() except smtplib.SMTPException: pass else: smtp.ehlo() try: smtp.sendmail(sender, recipients, message.as_string()) for recipient in recipients: logging.info("%s invited %s to wiki %s" % (sender, recipients, request.cfg.interwikiname)) except (smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused), error: if not getattr(request.cfg, "mail_login", None): raise error smtp.login(*request.cfg.mail_login.split(" ", 1)) smtp.sendmail(sender, recipients, message.as_string()) except Exception, exc: raise InviteException("Could not send the mail: %r" % exc) finally: try: smtp.quit() except Exception: pass
def check_locals(self, map_names): from email.Utils import getaddresses pairs = getaddresses(self.message.get_all('From', []) + self.message.get_all('To', []) + self.message.get_all('Cc', [])) for pair in pairs: if pair is not None and pair[1]: address = pair[1].lower() fields = address.split('@') if len(fields) != 2: self.checker.reject("Invalid address `%s'." % address) continue user, domain = fields if domain in self.run.heres: for item in user, address: for map_name in map_names: value = self.checker.get_value( map_name, item, "Rejected domain `%s'." % domain) if value is not None: break if value is not None: break else: self.checker.reject("Address `%s' locally unknown." % pair[1]) for pair in pairs: if pair is not None and pair[1]: user_domain = pair[1].split('@') if len(user_domain) == 2: user, domain = user_domain if self.progressive_lookup(user, domain, map_names): break else: if not self.run.silence_locals: self.checker.reject("Neither origin nor destination is local.")
def split_mail(msg, fail_on_empty=False): """Deconstruct an email.Message object into a sequence of words; returns an iterator for this sequence. If fail_on_empty is true, then ValueError is raised if no parseable message parts could be found. This function can only handle text parts, and does not attempt to extract meaningful data from other content types. """ coll = set() results = [coll] for tag, hdr in (('from', 'from'), ('from', 'sender'), ('rcpt', 'to'), ('rcpt', 'cc'), ('rcpt', 'bcc')): for name, addr in getaddresses(msg.get_all(hdr, ())): addr = addr.split('@', 1) coll.add('%s:@%s' % (tag, addr[-1].lower())) if len(addr) > 1: coll.add('%s:%s' % (tag, addr[0].lower())) proc = compose(unfold_entities, word_split, lowercase, remove_stopwords, dropwhile(lambda s: s in ('re', 'fwd')), add_prefix('subj', ':')) results.append(proc(msg.get('subject', ''))) found = 0 for part in msg.walk(): if part.get_content_maintype() != 'text': continue # skip non-text parts enc = part.get('content-transfer-encoding') if enc is None: text = part.get_payload() elif enc.lower() == 'quoted-printable': text = quopri.decodestring(part.get_payload()) elif enc.lower() == 'base64': text = base64.decodestring(part.get_payload()) elif enc.lower() in (None, '7bit', '8bit', 'binary'): text = part.get_payload() else: continue # unknown encoding method # Since the content may be encoded with some weird character # set, we'll try to get it back to Unicode so the XML parser # doesn't choke too hard. for cs in part.get_charsets(): if not cs: continue try: text = text.decode(cs) break except UnicodeDecodeError: continue else: # If we can't decode it some other way, try ISO 8859-1, # which subsubmes US ASCII anyway. text = text.decode('latin1') proc = compose(unfold_entities, word_split, lowercase, remove_stopwords, crush_urls, limit_length(100)) sub = part.get_content_subtype() if sub == 'html': proc = compose(strip_html, proc) elif sub not in ('plain', 'enriched'): continue # unknown text type results.append(proc(text)) found += 1 if found == 0 and fail_on_empty: raise ValueError("No parseable content found") return itertools.chain(*results)
def process(mlist, msg, msgdata): # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): del msg['x-ack'] msg['X-Ack'] = 'no' # Because we're going to modify various important headers in the email # message, we want to save some of the information in the msgdata # dictionary for later. Specifically, the sender header will get waxed, # but we need it for the Acknowledge module later. msgdata['original_sender'] = msg.get_sender() # VirginRunner sets _fasttrack for internally crafted messages. fasttrack = msgdata.get('_fasttrack') if not msgdata.get('isdigest') and not fasttrack: try: prefix_subject(mlist, msg, msgdata) except (UnicodeError, ValueError): # TK: Sometimes subject header is not MIME encoded for 8bit # simply abort prefixing. pass # Mark message so we know we've been here, but leave any existing # X-BeenThere's intact. msg['X-BeenThere'] = mlist.GetListEmail() # Add Precedence: and other useful headers. None of these are standard # and finding information on some of them are fairly difficult. Some are # just common practice, and we'll add more here as they become necessary. # Good places to look are: # # http://www.dsv.su.se/~jpalme/ietf/jp-ietf-home.html # http://www.faqs.org/rfcs/rfc2076.html # # None of these headers are added if they already exist. BAW: some # consider the advertising of this a security breach. I.e. if there are # known exploits in a particular version of Mailman and we know a site is # using such an old version, they may be vulnerable. It's too easy to # edit the code to add a configuration variable to handle this. if not msg.has_key('x-mailman-version'): msg['X-Mailman-Version'] = mm_cfg.VERSION # We set "Precedence: list" because this is the recommendation from the # sendmail docs, the most authoritative source of this header's semantics. if not msg.has_key('precedence'): msg['Precedence'] = 'list' # Reply-To: munging. Do not do this if the message is "fast tracked", # meaning it is internally crafted and delivered to a specific user. BAW: # Yuck, I really hate this feature but I've caved under the sheer pressure # of the (very vocal) folks want it. OTOH, RFC 2822 allows Reply-To: to # be a list of addresses, so instead of replacing the original, simply # augment it. RFC 2822 allows max one Reply-To: header so collapse them # if we're adding a value, otherwise don't touch it. (Should we collapse # in all cases?) if not fasttrack: # A convenience function, requires nested scopes. pair is (name, addr) new = [] d = {} def add(pair): lcaddr = pair[1].lower() if d.has_key(lcaddr): return d[lcaddr] = pair new.append(pair) # List admin wants an explicit Reply-To: added if mlist.reply_goes_to_list == 2: add(parseaddr(mlist.reply_to_address)) # If we're not first stripping existing Reply-To: then we need to add # the original Reply-To:'s to the list we're building up. In both # cases we'll zap the existing field because RFC 2822 says max one is # allowed. if not mlist.first_strip_reply_to: orig = msg.get_all('reply-to', []) for pair in getaddresses(orig): add(pair) # Set Reply-To: header to point back to this list. Add this last # because some folks think that some MUAs make it easier to delete # addresses from the right than from the left. if mlist.reply_goes_to_list == 1: i18ndesc = uheader(mlist, mlist.description, 'Reply-To') add((str(i18ndesc), mlist.GetListEmail())) del msg['reply-to'] # Don't put Reply-To: back if there's nothing to add! if new: # Preserve order msg['Reply-To'] = COMMASPACE.join( [formataddr(pair) for pair in new]) # The To field normally contains the list posting address. However # when messages are fully personalized, that header will get # overwritten with the address of the recipient. We need to get the # posting address in one of the recipient headers or they won't be # able to reply back to the list. It's possible the posting address # was munged into the Reply-To header, but if not, we'll add it to a # Cc header. BAW: should we force it into a Reply-To header in the # above code? # Also skip Cc if this is an anonymous list as list posting address # is already in From and Reply-To in this case. if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \ and not mlist.anonymous_list: # Watch out for existing Cc headers, merge, and remove dups. Note # that RFC 2822 says only zero or one Cc header is allowed. new = [] d = {} for pair in getaddresses(msg.get_all('cc', [])): add(pair) i18ndesc = uheader(mlist, mlist.description, 'Cc') add((str(i18ndesc), mlist.GetListEmail())) del msg['Cc'] msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new]) # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only # if the message is being crafted for a specific list (e.g. not for the # password reminders). # # BAW: Some people really hate the List-* headers. It seems that the free # version of Eudora (possibly on for some platforms) does not hide these # headers by default, pissing off their users. Too bad. Fix the MUAs. if msgdata.get('_nolist') or not mlist.include_rfc2369_headers: return # This will act like an email address for purposes of formataddr() listid = '%s.%s' % (mlist.internal_name(), mlist.host_name) cset = Utils.GetCharSet(mlist.preferred_language) if mlist.description: # Don't wrap the header since here we just want to get it properly RFC # 2047 encoded. i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998) listid_h = formataddr((str(i18ndesc), listid)) else: # without desc we need to ensure the MUST brackets listid_h = '<%s>' % listid # We always add a List-ID: header. del msg['list-id'] msg['List-Id'] = listid_h # For internally crafted messages, we also add a (nonstandard), # "X-List-Administrivia: yes" header. For all others (i.e. those coming # from list posts), we add a bunch of other RFC 2369 headers. requestaddr = mlist.GetRequestEmail() subfieldfmt = '<%s>, <mailto:%s?subject=%ssubscribe>' listinfo = mlist.GetScriptURL('listinfo', absolute=1) useropts = mlist.GetScriptURL('options', absolute=1) headers = {} if msgdata.get('reduced_list_headers'): headers['X-List-Administrivia'] = 'yes' else: headers.update({ 'List-Help': '<mailto:%s?subject=help>' % requestaddr, 'List-Unsubscribe': subfieldfmt % (useropts, requestaddr, 'un'), 'List-Subscribe': subfieldfmt % (listinfo, requestaddr, ''), }) # List-Post: is controlled by a separate attribute if mlist.include_list_post_header: headers['List-Post'] = '<mailto:%s>' % mlist.GetListEmail() # Add this header if we're archiving if mlist.archive: archiveurl = mlist.GetBaseArchiveURL() headers['List-Archive'] = '<%s>' % archiveurl # First we delete any pre-existing headers because the RFC permits only # one copy of each, and we want to be sure it's ours. for h, v in headers.items(): del msg[h] # Wrap these lines if they are too long. 78 character width probably # shouldn't be hardcoded, but is at least text-MUA friendly. The # adding of 2 is for the colon-space separator. if len(h) + 2 + len(v) > 78: v = CONTINUATION.join(v.split(', ')) msg[h] = v
def process(msg): all = msg.get_all('x-failed-recipients', []) return [a for n, a in getaddresses(all)]
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.lower().startswith(('re:', rsp.lower())): subject = rsp + subject envelope.add('Subject', subject) # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] my_addresses = settings.get_addresses() sender_address = parseaddr(sender)[1] cc = '' # check if reply is to self sent message if sender_address in my_addresses: recipients = [mail['To']] emsg = 'Replying to own message, set recipients to: %s' \ % recipients logging.debug(emsg) else: recipients = [sender] if self.groupreply: # make sure that our own address is not included # if the message was self-sent, then our address is not included MFT = mail.get_all('Mail-Followup-To', []) followupto = self.clear_my_address(my_addresses, MFT) if followupto and settings.get('honor_followup_to'): logging.debug('honor followup to: %s', followupto) recipients = [followupto] # since Mail-Followup-To was set, ignore the Cc header else: if sender != mail['From']: recipients.append(mail['From']) # append To addresses if not replying to self sent message if sender_address not in my_addresses: cleared = self.clear_my_address(my_addresses, mail.get_all('To', [])) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address(my_addresses, mail.get_all('Cc', [])) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) envelope.add('To', decode_header(to)) # if any of the recipients is a mailinglist that we are subscribed to, # set Mail-Followup-To header so that duplicates are avoided if settings.get('followup_to'): # to and cc are already cleared of our own address allrecipients = [to] + [cc] lists = settings.get('mailinglists') # check if any recipient address matches a known mailing list if any([addr in lists for n, addr in getaddresses(allrecipients)]): followupto = ', '.join(allrecipients) logging.debug('mail followup to: %s' % followupto) envelope.add('Mail-Followup-To', decode_header(followupto)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose ui.apply_command( ComposeCommand(envelope=envelope, spawn=self.force_spawn))
def process(mlist, msg, msgdata): # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): change_header('X-Ack', 'no', mlist, msg, msgdata) # Because we're going to modify various important headers in the email # message, we want to save some of the information in the msgdata # dictionary for later. Specifically, the sender header will get waxed, # but we need it for the Acknowledge module later. # We may have already saved it; if so, don't clobber it here. if 'original_sender' not in msgdata: msgdata['original_sender'] = msg.get_sender() # VirginRunner sets _fasttrack for internally crafted messages. fasttrack = msgdata.get('_fasttrack') if not msgdata.get('isdigest') and not fasttrack: try: prefix_subject(mlist, msg, msgdata) except (UnicodeError, ValueError): # TK: Sometimes subject header is not MIME encoded for 8bit # simply abort prefixing. pass # Mark message so we know we've been here, but leave any existing # X-BeenThere's intact. change_header('X-BeenThere', mlist.GetListEmail(), mlist, msg, msgdata, delete=False) # Add Precedence: and other useful headers. None of these are standard # and finding information on some of them are fairly difficult. Some are # just common practice, and we'll add more here as they become necessary. # Good places to look are: # # http://www.dsv.su.se/~jpalme/ietf/jp-ietf-home.html # http://www.faqs.org/rfcs/rfc2076.html # # None of these headers are added if they already exist. BAW: some # consider the advertising of this a security breach. I.e. if there are # known exploits in a particular version of Mailman and we know a site is # using such an old version, they may be vulnerable. It's too easy to # edit the code to add a configuration variable to handle this. change_header('X-Mailman-Version', mm_cfg.VERSION, mlist, msg, msgdata, repl=False) # We set "Precedence: list" because this is the recommendation from the # sendmail docs, the most authoritative source of this header's semantics. change_header('Precedence', 'list', mlist, msg, msgdata, repl=False) # Do we change the from so the list takes ownership of the email if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack: # Be as robust as possible here. faddrs = getaddresses(msg.get_all('from', [])) # Strip the nulls and bad emails. faddrs = [x for x in faddrs if x[1].find('@') > 0] if len(faddrs) == 1: realname, email = o_from = faddrs[0] else: # No From: or multiple addresses. Just punt and take # the get_sender result. realname = '' email = msgdata['original_sender'] o_from = (realname, email) if not realname: if mlist.isMember(email): realname = mlist.getMemberName(email) or email else: realname = email # Remove domain from realname if it looks like an email address realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) # Make a display name and RFC 2047 encode it if necessary. This is # difficult and kludgy. If the realname came from From: it should be # ascii or RFC 2047 encoded. If it came from the list, it should be # in the charset of the list's preferred language or possibly unicode. # if it's from the email address, it should be ascii. In any case, # make it a unicode. if isinstance(realname, unicode): urn = realname else: rn, cs = ch_oneline(realname) urn = unicode(rn, cs, errors='replace') # likewise, the list's real_name which should be ascii, but use the # charset of the list's preferred_language which should be a superset. lcs = Utils.GetCharSet(mlist.preferred_language) ulrn = unicode(mlist.real_name, lcs, errors='replace') # get translated 'via' with dummy replacements realname = '%(realname)s' lrn = '%(lrn)s' # We want the i18n context to be the list's preferred_language. It # could be the poster's. otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) via = _('%(realname)s via %(lrn)s') i18n.set_translation(otrans) uvia = unicode(via, lcs, errors='replace') # Replace the dummy replacements. uvia = re.sub(u'%\(lrn\)s', ulrn, re.sub(u'%\(realname\)s', urn, uvia)) # And get an RFC 2047 encoded header string. dn = str(Header(uvia, lcs)) change_header('From', formataddr((dn, mlist.GetListEmail())), mlist, msg, msgdata) else: # Use this as a flag o_from = None # Reply-To: munging. Do not do this if the message is "fast tracked", # meaning it is internally crafted and delivered to a specific user. BAW: # Yuck, I really hate this feature but I've caved under the sheer pressure # of the (very vocal) folks want it. OTOH, RFC 2822 allows Reply-To: to # be a list of addresses, so instead of replacing the original, simply # augment it. RFC 2822 allows max one Reply-To: header so collapse them # if we're adding a value, otherwise don't touch it. (Should we collapse # in all cases?) # MAS: We need to do some things with the original From: if we've munged # it for DMARC mitigation. We have goals for this process which are # not completely compatible, so we do the best we can. Our goals are: # 1) as long as the list is not anonymous, the original From: address # should be obviously exposed, i.e. not just in a header that MUAs # don't display. # 2) the original From: address should not be in a comment or display # name in the new From: because it is claimed that multiple domains # in any fields in From: are indicative of spamminess. This means # it should be in Reply-To: or Cc:. # 3) the behavior of an MUA doing a 'reply' or 'reply all' should be # consistent regardless of whether or not the From: is munged. # Goal 3) implies sometimes the original From: should be in Reply-To: # and sometimes in Cc:, and even so, this goal won't be achieved in # all cases with all MUAs. In cases of conflict, the above ordering of # goals is priority order. if not fasttrack: # A convenience function, requires nested scopes. pair is (name, addr) new = [] d = {} def add(pair): lcaddr = pair[1].lower() if d.has_key(lcaddr): return d[lcaddr] = pair new.append(pair) # List admin wants an explicit Reply-To: added if mlist.reply_goes_to_list == 2: add(parseaddr(mlist.reply_to_address)) # If we're not first stripping existing Reply-To: then we need to add # the original Reply-To:'s to the list we're building up. In both # cases we'll zap the existing field because RFC 2822 says max one is # allowed. o_rt = False if not mlist.first_strip_reply_to: orig = msg.get_all('reply-to', []) for pair in getaddresses(orig): # There's an original Reply-To: and we're not removing it. add(pair) o_rt = True # We also need to put the old From: in Reply-To: in all cases where # it is not going in Cc:. This is when reply_goes_to_list == 0 and # either there was no original Reply-To: or we stripped it. # However, if there was an original Reply-To:, unstripped, and it # contained the original From: address we need to flag that it's # there so we don't add the original From: to Cc: if o_from and mlist.reply_goes_to_list == 0: if o_rt: if d.has_key(o_from[1].lower()): # Original From: address is in original Reply-To:. # Pretend we added it. o_from = None else: add(o_from) # Flag that we added it. o_from = None # Set Reply-To: header to point back to this list. Add this last # because some folks think that some MUAs make it easier to delete # addresses from the right than from the left. if mlist.reply_goes_to_list == 1: i18ndesc = uheader(mlist, mlist.description, 'Reply-To') add((str(i18ndesc), mlist.GetListEmail())) # Don't put Reply-To: back if there's nothing to add! if new: # Preserve order change_header('Reply-To', COMMASPACE.join([formataddr(pair) for pair in new]), mlist, msg, msgdata) else: del msg['reply-to'] # The To field normally contains the list posting address. However # when messages are fully personalized, that header will get # overwritten with the address of the recipient. We need to get the # posting address in one of the recipient headers or they won't be # able to reply back to the list. It's possible the posting address # was munged into the Reply-To header, but if not, we'll add it to a # Cc header. BAW: should we force it into a Reply-To header in the # above code? # Also skip Cc if this is an anonymous list as list posting address # is already in From and Reply-To in this case. # We do add the Cc in cases where From: header munging is being done # because even though the list address is in From:, the Reply-To: # poster will override it. Brain dead MUAs may then address the list # twice on a 'reply all', but reasonable MUAs should do the right # thing. We also add the original From: to Cc: if it wasn't added # to Reply-To: add_list = (mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 and not mlist.anonymous_list) if add_list or o_from: # Watch out for existing Cc headers, merge, and remove dups. Note # that RFC 2822 says only zero or one Cc header is allowed. new = [] d = {} # If we're adding the original From:, add it first. if o_from: add(o_from) # AvoidDuplicates may have set a new Cc: in msgdata.add_header, # so check that. if (msgdata.has_key('add_header') and msgdata['add_header'].has_key('Cc')): for pair in getaddresses([msgdata['add_header']['Cc']]): add(pair) else: for pair in getaddresses(msg.get_all('cc', [])): add(pair) if add_list: i18ndesc = uheader(mlist, mlist.description, 'Cc') add((str(i18ndesc), mlist.GetListEmail())) change_header('Cc', COMMASPACE.join([formataddr(pair) for pair in new]), mlist, msg, msgdata) # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only # if the message is being crafted for a specific list (e.g. not for the # password reminders). # # BAW: Some people really hate the List-* headers. It seems that the free # version of Eudora (possibly on for some platforms) does not hide these # headers by default, pissing off their users. Too bad. Fix the MUAs. if msgdata.get('_nolist') or not mlist.include_rfc2369_headers: return # This will act like an email address for purposes of formataddr() listid = '%s.%s' % (mlist.internal_name(), mlist.host_name) cset = Utils.GetCharSet(mlist.preferred_language) if mlist.description: # Don't wrap the header since here we just want to get it properly RFC # 2047 encoded. i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998) listid_h = formataddr((str(i18ndesc), listid)) else: # without desc we need to ensure the MUST brackets listid_h = '<%s>' % listid # We always add a List-ID: header. change_header('List-Id', listid_h, mlist, msg, msgdata) # For internally crafted messages, we also add a (nonstandard), # "X-List-Administrivia: yes" header. For all others (i.e. those coming # from list posts), we add a bunch of other RFC 2369 headers. requestaddr = mlist.GetRequestEmail() subfieldfmt = '<%s>, <mailto:%s?subject=%ssubscribe>' listinfo = mlist.GetScriptURL('listinfo', absolute=1) useropts = mlist.GetScriptURL('options', absolute=1) headers = {} if msgdata.get('reduced_list_headers'): headers['X-List-Administrivia'] = 'yes' else: headers.update({ 'List-Help': '<mailto:%s?subject=help>' % requestaddr, 'List-Unsubscribe': subfieldfmt % (useropts, requestaddr, 'un'), 'List-Subscribe': subfieldfmt % (listinfo, requestaddr, ''), }) # List-Post: is controlled by a separate attribute if mlist.include_list_post_header: headers['List-Post'] = '<mailto:%s>' % mlist.GetListEmail() # Add this header if we're archiving if mlist.archive: archiveurl = mlist.GetBaseArchiveURL() headers['List-Archive'] = '<%s>' % archiveurl # First we delete any pre-existing headers because the RFC permits only # one copy of each, and we want to be sure it's ours. for h, v in headers.items(): # Wrap these lines if they are too long. 78 character width probably # shouldn't be hardcoded, but is at least text-MUA friendly. The # adding of 2 is for the colon-space separator. if len(h) + 2 + len(v) > 78: v = CONTINUATION.join(v.split(', ')) change_header(h, v, mlist, msg, msgdata)
def get_addrs(message, header): """ get a list of tuples (realname, mailaddr) from the specified header """ dec_hdr = [decode_2044(hdr) for hdr in message.get_all(header, [])] return getaddresses(dec_hdr)
assert value == mm_cfg.DISCARD # Discarded rejection = 'Discarded' # Forward the message if forward and addr: # If we've approved the message, we need to be sure to craft a # completely unique second message for the forwarding operation, # since we don't want to share any state or information with the # normal delivery. try: copy = readMessage(path) except IOError, e: if e.errno <> errno.ENOENT: raise raise Errors.LostHeldMessage(path) # It's possible the addr is a comma separated list of addresses. addrs = getaddresses([addr]) if len(addrs) == 1: realname, addr = addrs[0] # If the address getting the forwarded message is a member of # the list, we want the headers of the outer message to be # encoded in their language. Otherwise it'll be the preferred # language of the mailing list. lang = self.getMemberLanguage(addr) else: # Throw away the realnames addr = [a for realname, a in addrs] # Which member language do we attempt to use? We could use # the first match or the first address, but in the face of # ambiguity, let's just use the list's preferred language lang = self.preferred_language otrans = i18n.get_translation()
def process(mlist, msg, msgdata): recips = msgdata['recips'] # Short circuit if not recips: return # There is an issue with addresses in To: or Cc: that differ in # case from the MemberCPAddresses in recips. We can't just # lower-case everything because we still want CP addresses in # the final recips list, so we lower case the keys. # Seed this set with addresses we don't care about dup avoiding explicit_recips = {} listaddrs = [ mlist.GetListEmail(), mlist.GetBouncesEmail(), mlist.GetOwnerEmail(), mlist.GetRequestEmail() ] for addr in listaddrs: explicit_recips[addr.lower()] = True # Figure out the set of explicit recipients ccaddrs = {} for header in ('to', 'cc', 'resent-to', 'resent-cc'): addrs = getaddresses(msg.get_all(header, [])) if header == 'cc': for name, addr in addrs: ccaddrs[addr.lower()] = name, addr for name, addr in addrs: if not addr: continue # Ignore the list addresses for purposes of dup avoidance explicit_recips[addr.lower()] = True # Now strip out the list addresses for addr in listaddrs: del explicit_recips[addr.lower()] if not explicit_recips: # No one was explicitly addressed, so we can't do any dup collapsing return newrecips = [] for r in recips: # If this recipient is explicitly addressed... if explicit_recips.has_key(r.lower()): send_duplicate = True # If the member wants to receive duplicates, or if the recipient # is not a member at all, just flag the X-Mailman-Duplicate: yes # header. if mlist.isMember(r) and \ mlist.getMemberOption(r, mm_cfg.DontReceiveDuplicates): send_duplicate = False # We'll send a duplicate unless the user doesn't wish it. If # personalization is enabled, the add-dupe-header flag will add a # X-Mailman-Duplicate: yes header for this user's message. if send_duplicate: msgdata.setdefault('add-dup-header', {})[r] = True newrecips.append(r) elif ccaddrs.has_key(r.lower()): del ccaddrs[r.lower()] else: # Otherwise, this is the first time they've been in the recips # list. Add them to the newrecips list and flag them as having # received this message. newrecips.append(r) # Set the new list of recipients msgdata['recips'] = newrecips # RFC 2822 specifies zero or one CC header del msg['cc'] if ccaddrs: msg['Cc'] = COMMASPACE.join([formataddr(i) for i in ccaddrs.values()])
fp=open(file,"r") mbox = mailbox.UnixMailbox(fp, msgfactory) # parse unix mailbox G=nx.MultiDiGraph() # create empty graph # parse each messages and build graph for msg in mbox: # msg is python email.Message.Message object (source_name,source_addr) = parseaddr(msg['From']) # sender # get all recipients # see http://www.python.org/doc/current/lib/module-email.Utils.html tos = msg.get_all('to', []) ccs = msg.get_all('cc', []) resent_tos = msg.get_all('resent-to', []) resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) # now add the edges for this mail message for (target_name,target_addr) in all_recipients: G.add_edge(source_addr,target_addr,message=msg) # print edges with message subject for (u,v,d) in G.edges_iter(data=True): print "From: %s To: %s Subject: %s"%(u,v,d['message']["Subject"]) try: # draw pos=nx.spring_layout(G,iterations=10) nx.draw(G,pos,node_size=0,alpha=0.4,edge_color='r',font_size=16) plt.savefig("unix_email.png") plt.show() except: # matplotlib not available
def _process(self, kw): # sort out what encoding we're going to use encoding = kw.get('encoding', self.getProperty('encoding', default_encoding)) text = self.__class__.__bases__[1].__call__(self, **kw) # ZPT adds newline at the end, but it breaks backward compatibility. # So I remove it. if text.endswith('\n'): text = text[:-1] if not self.html() and isinstance(text, unicode): text = text.encode(encoding, 'replace') # now turn the result into a MIMEText object msg = MIMEText(text.replace('\r', ''), self.content_type.split('/')[1], encoding) # sort out what headers and addresses we're going to use headers = {} values = {} # headers from the headers property for header in getattr(self, 'headers', ()): name, value = header.split(':', 1) headers[name] = value # headers from the headers parameter headers_param = kw.get('headers', {}) headers.update(headers_param) # values and some specific headers for key, header in (('mfrom', 'From'), ('mto', 'To'), ('mcc', 'Cc'), ('mbcc', 'Bcc'), ('subject', 'Subject')): value = kw.get( key, headers_param.get(header, getattr(self, key, headers.get(header)))) if value is not None: values[key] = value if key == 'subject': try: # Try to keep header non encoded value = Header(value) except UnicodeDecodeError: value = Header(value, "UTF-8") else: dest_list = [] for name, email in getaddresses( (value, ) if isinstance(value, basestring) else value): try: name = Header(name) except UnicodeDecodeError: name = Header(name, "UTF-8") dest_list.append(formataddr((name.encode(), email))) value = ", ".join(dest_list) headers[header] = value # check required values have been supplied errors = [] for param in ('mfrom', 'mto'): if not values.get(param): errors.append(param) if errors: raise TypeError( 'The following parameters were required by not specified: ' + (', '.join(errors))) # add date header headers['Date'] = DateTime().rfc822() # do not let the MTA to generate the Message-ID: # we want to have it stored in ERP5, for mail threading headers['Message-ID'] = make_msgid() # turn headers into an ordered list for predictable header order keys = headers.keys() keys.sort() return msg, values, [(key, headers[key]) for key in keys]
def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() # set body text name, address = self.message.get_author() timestamp = self.message.get_date() qf = settings.get_hook('reply_prefix') if qf: quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman) else: quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp) mailcontent = quotestring quotehook = settings.get_hook('text_quote') if quotehook: mailcontent += quotehook(self.message.accumulate_body()) else: quote_prefix = settings.get('quote_prefix') for line in self.message.accumulate_body().splitlines(): mailcontent += quote_prefix + line + '\n' envelope = Envelope(bodytext=mailcontent) # copy subject subject = decode_header(mail.get('Subject', '')) reply_subject_hook = settings.get_hook('reply_subject') if reply_subject_hook: subject = reply_subject_hook(subject) else: rsp = settings.get('reply_subject_prefix') if not subject.lower().startswith(('re:', rsp.lower())): subject = rsp + subject envelope.add('Subject', subject) # Auto-detect ML auto_replyto_mailinglist = settings.get('auto_replyto_mailinglist') if mail['List-Id'] and self.listreply is None: # mail['List-Id'] is need to enable reply-to-list self.listreply = auto_replyto_mailinglist elif mail['List-Id'] and self.listreply is True: self.listreply = True elif self.listreply is False: # In this case we only need the sender self.listreply = False # set From-header and sending account try: from_header, account = determine_sender(mail, 'reply') except AssertionError as e: ui.notify(e.message, priority='error') return envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] my_addresses = settings.get_addresses() sender_address = parseaddr(sender)[1] cc = '' # check if reply is to self sent message if sender_address in my_addresses: recipients = [mail['To']] emsg = 'Replying to own message, set recipients to: %s' \ % recipients logging.debug(emsg) else: recipients = [sender] if self.groupreply: # make sure that our own address is not included # if the message was self-sent, then our address is not included MFT = mail.get_all('Mail-Followup-To', []) followupto = self.clear_my_address(my_addresses, MFT) if followupto and settings.get('honor_followup_to'): logging.debug('honor followup to: %s', followupto) recipients = [followupto] # since Mail-Followup-To was set, ignore the Cc header else: if sender != mail['From']: recipients.append(mail['From']) # append To addresses if not replying to self sent message if sender_address not in my_addresses: cleared = self.clear_my_address( my_addresses, mail.get_all('To', [])) recipients.append(cleared) # copy cc for group-replies if 'Cc' in mail: cc = self.clear_my_address( my_addresses, mail.get_all('Cc', [])) envelope.add('Cc', decode_header(cc)) to = ', '.join(recipients) logging.debug('reply to: %s' % to) if self.listreply: # To choose the target of the reply --list # Reply-To is standart reply target RFC 2822:, RFC 1036: 2.2.1 # X-BeenThere is needed by sourceforge ML also winehq # X-Mailing-List is also standart and is used by git-send-mail to = mail['Reply-To'] or mail['X-BeenThere'] or mail['X-Mailing-List'] # Some mail server (gmail) will not resend you own mail, so you have # to deal with the one in sent if to is None: to = mail['To'] logging.debug('mail list reply to: %s' % to) # Cleaning the 'To' in this case if envelope.get('To') is not None: envelope.__delitem__('To') # Finally setup the 'To' header envelope.add('To', decode_header(to)) # if any of the recipients is a mailinglist that we are subscribed to, # set Mail-Followup-To header so that duplicates are avoided if settings.get('followup_to'): # to and cc are already cleared of our own address allrecipients = [to] + [cc] lists = settings.get('mailinglists') # check if any recipient address matches a known mailing list if any([addr in lists for n, addr in getaddresses(allrecipients)]): followupto = ', '.join(allrecipients) logging.debug('mail followup to: %s' % followupto) envelope.add('Mail-Followup-To', decode_header(followupto)) # set In-Reply-To header envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id()) # set References header old_references = mail.get('References', '') if old_references: old_references = old_references.split() references = old_references[-8:] if len(old_references) > 8: references = old_references[:1] + references references.append('<%s>' % self.message.get_message_id()) envelope.add('References', ' '.join(references)) else: envelope.add('References', '<%s>' % self.message.get_message_id()) # continue to compose encrypt = mail.get_content_subtype() == 'encrypted' ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn, encrypt=encrypt))
def getAttachements(rfcmessage): container = [] message = email.message_from_string(rfcmessage) subject = decode_subject(message["subject"]) tos = message.get_all('to', []) ccs = message.get_all('cc', []) resent_tos = message.get_all('resent-to', []) resent_ccs = message.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) froms = message.get_all('from', []) try: message_id = message["message-id"] except: message_id = "not_unique" sender = getaddresses(froms) recepients = [] for to_whom_name, to_whom in all_recipients: try: recepients.append(USER_TO_URL_RE.match(to_whom).groupdict()) except: pass counter = 0 for msg in message.walk(): if not msg.is_multipart() and msg.get_content_maintype() != 'message': ct = msg.get_content_type() mtype = msg.get_content_maintype().lower() payload = msg.get_payload() te = msg.get('Content-Transfer-Encoding', '8bit').lower() if te == 'base64': try: payload = email.base64MIME.decode(payload) except: payload = '' # ??? elif te == 'quoted-printable': try: payload = email.quopriMIME.decode(payload) except: payload = '' # ??? if mtype == 'text' or mtype is None: try: tc = msg.get_content_charset() except: tc = 'latin-1' else: pass if mtype in ('image', 'video', 'audio', 'text', 'application'): counter += 1 name = dict(msg.get_params() or []).get('name', 'noname-%i.txt' % counter) container.append((ct, name, payload)) else: pass return Storage( sender=sender, recepients=recepients, subject=subject, message_id=message_id, container=container, )
def secureSend(self, message, mto, mfrom, subject='[No Subject]', mcc=None, mbcc=None, subtype='plain', charset='us-ascii', debug=False, **kwargs): """A more secure way to send a message message: The plain message text without any headers or an email.Message.Message based instance mto: To: field (string or list) mfrom: From: field subject: Message subject (default: [No Subject]) mcc: Cc: (carbon copy) field (string or list) mbcc: Bcc: (blind carbon copy) field (string or list) subtype: Content subtype of the email e.g. 'plain' for text/plain (ignored if message is a email.Message.Message instance) charset: Charset used for the email, subject and email addresses kwargs: Additional headers """ mto = self.emailListToString(mto) mcc = self.emailListToString(mcc) mbcc = self.emailListToString(mbcc) # validate email addresses # XXX check Return-Path for addr in mto, mcc, mbcc: if addr: result = self.validateEmailAddresses(addr) if not result: raise MailHostError, 'Invalid email address: %s' % addr result = self.validateSingleEmailAddress(mfrom) if not result: raise MailHostError, 'Invalid email address: %s' % mfrom # create message if isinstance(message, email.Message.Message): # got an email message. Make a deepcopy because we don't want to # change the message msg = deepcopy(message) else: if isinstance(message, unicode): message = message.encode(charset) msg = email.MIMEText.MIMEText(message, subtype, charset) mfrom = encodeHeaderAddress(mfrom, charset) mto = encodeHeaderAddress(mto, charset) mcc = encodeHeaderAddress(mcc, charset) mbcc = encodeHeaderAddress(mbcc, charset) # set important headers self.setHeaderOf(msg, skipEmpty=True, From=mfrom, To=mto, Subject=str(email.Header.Header(subject, charset)), Cc=mcc, Bcc=mbcc) for bad in BAD_HEADERS: if bad in kwargs: raise MailHostError, 'Header %s is forbidden' % bad self.setHeaderOf(msg, **kwargs) # we have to pass *all* recipient email addresses to the # send method because the smtp server doesn't add CC and BCC to # the list of recipients to = msg.get_all('to', []) cc = msg.get_all('cc', []) bcc = msg.get_all('bcc', []) #resent_tos = msg.get_all('resent-to', []) #resent_ccs = msg.get_all('resent-cc', []) recipient_list = getaddresses(to + cc + bcc) all_recipients = [formataddr(pair) for pair in recipient_list] # finally send email return self._send(mfrom, all_recipients, msg, debug=debug)
def send_i18n_digests(mlist, mboxfp): mbox = Mailbox(mboxfp) # Prepare common information (first lang/charset) lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) lcset_out = Charset(lcset).output_charset or lcset # Common Information (contd) realname = mlist.real_name volume = mlist.volume issue = mlist.next_digest_number digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') digestsubj = Header(digestid, lcset, header_name='Subject') # Set things up for the MIME digest. Only headers not added by # CookHeaders need be added here. # Date/Message-ID should be added here also. mimemsg = Message.Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() mimemsg['Subject'] = digestsubj mimemsg['To'] = mlist.GetListEmail() mimemsg['Reply-To'] = mlist.GetListEmail() mimemsg['Date'] = formatdate(localtime=1) mimemsg['Message-ID'] = Utils.unique_message_id(mlist) # Set things up for the rfc1153 digest plainmsg = StringIO() rfc1153msg = Message.Message() rfc1153msg['From'] = mlist.GetRequestEmail() rfc1153msg['Subject'] = digestsubj rfc1153msg['To'] = mlist.GetListEmail() rfc1153msg['Reply-To'] = mlist.GetListEmail() rfc1153msg['Date'] = formatdate(localtime=1) rfc1153msg['Message-ID'] = Utils.unique_message_id(mlist) separator70 = '-' * 70 separator30 = '-' * 30 # In the rfc1153 digest, the masthead contains the digest boilerplate plus # any digest header. In the MIME digests, the masthead and digest header # are separate MIME subobjects. In either case, it's the first thing in # the digest, and we can calculate it now, so go ahead and add it now. mastheadtxt = Utils.maketext( 'masthead.txt', { 'real_name': mlist.real_name, 'got_list_email': mlist.GetListEmail(), 'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'got_request_email': mlist.GetRequestEmail(), 'got_owner_email': mlist.GetOwnerEmail(), }, mlist=mlist) # MIME masthead = MIMEText(mastheadtxt, _charset=lcset) masthead['Content-Description'] = digestid mimemsg.attach(masthead) # RFC 1153 print >> plainmsg, mastheadtxt print >> plainmsg # Now add the optional digest header but only if more than whitespace. if re.sub('\s', '', mlist.digest_header): headertxt = decorate(mlist, mlist.digest_header, _('digest header')) # MIME header = MIMEText(headertxt, _charset=lcset) header['Content-Description'] = _('Digest Header') mimemsg.attach(header) # RFC 1153 print >> plainmsg, headertxt print >> plainmsg # Now we have to cruise through all the messages accumulated in the # mailbox file. We can't add these messages to the plainmsg and mimemsg # yet, because we first have to calculate the table of contents # (i.e. grok out all the Subjects). Store the messages in a list until # we're ready for them. # # Meanwhile prepare things for the table of contents toc = StringIO() print >> toc, _("Today's Topics:\n") # Now cruise through all the messages in the mailbox of digest messages, # building the MIME payload and core of the RFC 1153 digest. We'll also # accumulate Subject: headers and authors for the table-of-contents. messages = [] msgcount = 0 msg = mbox.next() while msg is not None: if msg == '': # It was an unparseable message msg = mbox.next() continue msgcount += 1 messages.append(msg) # Get the Subject header msgsubj = msg.get('subject', _('(no subject)')) subject = Utils.oneline(msgsubj, lcset) # Don't include the redundant subject prefix in the toc mo = re.match('(re:? *)?(%s)' % re.escape(mlist.subject_prefix), subject, re.IGNORECASE) if mo: subject = subject[:mo.start(2)] + subject[mo.end(2):] username = '' addresses = getaddresses([Utils.oneline(msg.get('from', ''), lcset)]) # Take only the first author we find if isinstance(addresses, ListType) and addresses: username = addresses[0][0] if not username: username = addresses[0][1] if username: username = '******' % username # Put count and Wrap the toc subject line wrapped = Utils.wrap('%2d. %s' % (msgcount, subject), 65) slines = wrapped.split('\n') # See if the user's name can fit on the last line if len(slines[-1]) + len(username) > 70: slines.append(username) else: slines[-1] += username # Add this subject to the accumulating topics first = True for line in slines: if first: print >> toc, ' ', line first = False else: print >> toc, ' ', line.lstrip() # We do not want all the headers of the original message to leak # through in the digest messages. For this phase, we'll leave the # same set of headers in both digests, i.e. those required in RFC 1153 # plus a couple of other useful ones. We also need to reorder the # headers according to RFC 1153. Later, we'll strip out headers for # for the specific MIME or plain digests. keeper = {} all_keepers = {} for header in (mm_cfg.MIME_DIGEST_KEEP_HEADERS + mm_cfg.PLAIN_DIGEST_KEEP_HEADERS): all_keepers[header] = True all_keepers = all_keepers.keys() for keep in all_keepers: keeper[keep] = msg.get_all(keep, []) # Now remove all unkempt headers :) for header in msg.keys(): del msg[header] # And add back the kept header in the RFC 1153 designated order for keep in all_keepers: for field in keeper[keep]: msg[keep] = field # And a bit of extra stuff msg['Message'] = ` msgcount ` # Get the next message in the digest mailbox msg = mbox.next() # Now we're finished with all the messages in the digest. First do some # sanity checking and then on to adding the toc. if msgcount == 0: # Why did we even get here? return toctext = to_cset_out(toc.getvalue(), lcset) # MIME tocpart = MIMEText(toctext, _charset=lcset) tocpart['Content-Description'] = _( "Today's Topics (%(msgcount)d messages)") mimemsg.attach(tocpart) # RFC 1153 print >> plainmsg, toctext print >> plainmsg # For RFC 1153 digests, we now need the standard separator print >> plainmsg, separator70 print >> plainmsg # Now go through and add each message mimedigest = MIMEBase('multipart', 'digest') mimemsg.attach(mimedigest) first = True for msg in messages: # MIME. Make a copy of the message object since the rfc1153 # processing scrubs out attachments. mimedigest.attach(MIMEMessage(copy.deepcopy(msg))) # rfc1153 if first: first = False else: print >> plainmsg, separator30 print >> plainmsg # Use Mailman.Handlers.Scrubber.process() to get plain text try: msg = scrubber(mlist, msg) except Errors.DiscardMessage: print >> plainmsg, _('[Message discarded by content filter]') continue # Honor the default setting for h in mm_cfg.PLAIN_DIGEST_KEEP_HEADERS: if msg[h]: uh = Utils.wrap('%s: %s' % (h, Utils.oneline(msg[h], lcset))) uh = '\n\t'.join(uh.split('\n')) print >> plainmsg, uh print >> plainmsg # If decoded payload is empty, this may be multipart message. # -- just stringfy it. payload = msg.get_payload(decode=True) \ or msg.as_string().split('\n\n',1)[1] mcset = msg.get_content_charset('') if mcset and mcset <> lcset and mcset <> lcset_out: try: payload = unicode(payload, mcset, 'replace').encode(lcset, 'replace') except (UnicodeError, LookupError): # TK: Message has something unknown charset. # _out means charset in 'outer world'. payload = unicode(payload, lcset_out, 'replace').encode(lcset, 'replace') print >> plainmsg, payload if not payload.endswith('\n'): print >> plainmsg # Now add the footer but only if more than whitespace. if re.sub('\s', '', mlist.digest_footer): footertxt = decorate(mlist, mlist.digest_footer, _('digest footer')) # MIME footer = MIMEText(footertxt, _charset=lcset) footer['Content-Description'] = _('Digest Footer') mimemsg.attach(footer) # RFC 1153 # MAS: There is no real place for the digest_footer in an RFC 1153 # compliant digest, so add it as an additional message with # Subject: Digest Footer print >> plainmsg, separator30 print >> plainmsg print >> plainmsg, 'Subject: ' + _('Digest Footer') print >> plainmsg print >> plainmsg, footertxt print >> plainmsg print >> plainmsg, separator30 print >> plainmsg # Do the last bit of stuff for each digest type signoff = _('End of ') + digestid # MIME # BAW: This stuff is outside the normal MIME goo, and it's what the old # MIME digester did. No one seemed to complain, probably because you # won't see it in an MUA that can't display the raw message. We've never # got complaints before, but if we do, just wax this. It's primarily # included for (marginally useful) backwards compatibility. mimemsg.postamble = signoff # rfc1153 print >> plainmsg, signoff print >> plainmsg, '*' * len(signoff) # Do our final bit of housekeeping, and then send each message to the # outgoing queue for delivery. mlist.next_digest_number += 1 virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) # Calculate the recipients lists plainrecips = [] mimerecips = [] drecips = mlist.getDigestMemberKeys() + mlist.one_last_digest.keys() for user in mlist.getMemberCPAddresses(drecips): # user might be None if someone who toggled off digest delivery # subsequently unsubscribed from the mailing list. Also, filter out # folks who have disabled delivery. if user is None or mlist.getDeliveryStatus(user) <> ENABLED: continue # Otherwise, decide whether they get MIME or RFC 1153 digests if mlist.getMemberOption(user, mm_cfg.DisableMime): plainrecips.append(user) else: mimerecips.append(user) # Zap this since we're now delivering the last digest to these folks. mlist.one_last_digest.clear() # MIME virginq.enqueue(mimemsg, recips=mimerecips, listname=mlist.internal_name(), isdigest=True) # RFC 1153 rfc1153msg.set_payload(to_cset_out(plainmsg.getvalue(), lcset), lcset) virginq.enqueue(rfc1153msg, recips=plainrecips, listname=mlist.internal_name(), isdigest=True)