def send_email(sender, recipient, subject, body, host='localhost', port='25', username=None, password=None, header_charset='UTF-8'): for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') msg = MIMEText(body.encode(body_charset), 'plain', body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) smtp = SMTP('{host}:{port}'.format(host=host, port=port)) smtp.starttls() if username and password: smtp.login(username,password) smtp.sendmail(sender, recipient, msg.as_string()) smtp.quit()
def send(self): body = self.template.render(**self.vars) header_charset = 'ISO-8859-1' body_charset = 'UTF-8' sender_name, sender_addr = parseaddr(self.sender) recipient_name, recipient_addr = parseaddr(self.recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body, 'plain', body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(self.subject), header_charset) with SMTP() as smtp: smtp.connect(self.smtp) smtp.sendmail(self.sender, self.recipient, msg.as_string())
def sendEmail(server, port, login, password, sender, recipient, subject, body): """Send e-mail function with unicode support""" headerCharset = "ISO-8859-1" for bodyCharset in ("US-ASCII", "ISO-8859-1", "UTF-8"): try: body.encode(bodyCharset) except UnicodeError: pass else: break senderName, senderAddr = parseaddr(sender) recipientName, recipientAddr = parseaddr(recipient) senderName = str(Header(unicode(senderName), headerCharset)) recipientName = str(Header(unicode(recipientName), headerCharset)) senderAddr = senderAddr.encode("ascii") recipientAddr = recipientAddr.encode("ascii") msg = MIMEText(body.encode(bodyCharset), "html", bodyCharset) msg["From"] = formataddr((senderName, senderAddr)) msg["To"] = formataddr((recipientName, recipientAddr)) msg["Subject"] = Header(unicode(subject), headerCharset) server = smtplib.SMTP(server, port) server.login(login, password) server.sendmail(sender, recipient, msg.as_string()) server.quit()
def send_email(sender, recipient, subject, body): from smtplib import SMTP from email.MIMEText import MIMEText from email.Header import Header from email.Utils import parseaddr, formataddr # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'UTF-8', 'ISO-8859-1', 'US-ASCII' : try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), 'plain', body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) # Send the message via SMTP to localhost:25 smtp = SMTP("localhost") smtp.sendmail(sender, recipient, msg.as_string()) smtp.quit()
def send_email(recipient, subject, body, sender=u'Компьютерный магазин <*****@*****.**>'): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP to localhost port 25. This is easy to change if you want something different. The charset of the email will be the first one out of US-ASCII, ISO-8859-1 and UTF-8 that can represent all the characters occurring in the email. """ # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) #msg = MIMEText(body.encode(body_charset), 'plain', body_charset) msg = MIMEMultipart('alternative') part = MIMEText(body.encode(body_charset), 'html', body_charset) msg.attach(part) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), body_charset) msg = StringIO(str(msg)) d = defer.Deferred() factory = ESMTPSenderFactory(sender_addr, mailpassword, sender_addr, recipient, msg, d, requireTransportSecurity=False) reactor.connectTCP('smtp.yandex.ru', 587, factory) return d
def send_email(sender, recipient, subject, body, content_type="plain"): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP to localhost port 25. This is easy to change if you want something different. The charset of the email will be the first one out of US-ASCII, ISO-8859-1 and UTF-8 that can represent all the characters occurring in the email. """ # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = "ISO-8859-1" # We must choose the body charset manually for body_charset in "US-ASCII", "ISO-8859-1", "UTF-8": try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode("ascii") recipient_addr = recipient_addr.encode("ascii") # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), content_type, body_charset) msg["From"] = formataddr((sender_name, sender_addr)) msg["To"] = formataddr((recipient_name, recipient_addr)) msg["Subject"] = Header(unicode(subject), header_charset) try: # Send the message via SMTP to localhost:25 smtp = SMTP("127.0.0.1") smtp.ehlo() smtp.sendmail(sender_addr, recipient, msg.as_string()) smtp.quit() except Exception as ex: pass
def get_maildata(sender, recipient, subject, body): # for python<2.7 # source: http://mg.pov.lt/blog/unicode-emails-in-python sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) sender_name = str(Header(unicode(sender_name), 'utf8')) recipient_name = str(Header(unicode(recipient_name), 'utf8')) sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') msg = MIMEText(body.encode('utf8'), 'html', 'utf8') msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), 'utf8') return msg.as_string()
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 sendmail(self, toaddr, subject, body, cc=[], bcc=[]): if not self.server: self.connect() logging.info( 'Send mail to %s' % toaddr ) for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break from_name, from_addr = parseaddr( self.fromaddr ) to_name , to_addr = parseaddr( toaddr ) from_name = str(Header(unicode(from_name), self.header_charset)) to_name = str(Header(unicode(to_name), self.header_charset)) from_addr = from_addr.encode('ascii') to_addr = to_addr.encode('ascii') if from_addr == to_addr: logging.info( 'Send mail to myself is not allowed now.') return email_format = 'html' if self.HTML else 'plain' msg = MIMEText( body.encode(body_charset), email_format, body_charset ) msg['From'] = formataddr((from_name, from_addr)) msg['To'] = formataddr((to_name, to_addr)) if cc: msg['CC'] = ', '.join([ self._formataddr(x) for x in cc ]) if bcc: msg['BCC'] = ', '.join([ self._formataddr(x) for x in bcc ]) msg['Subject'] = Header(unicode(subject), self.header_charset) msg['date'] = time.strftime('%a, %d %b %Y %H:%M:%S %z') try: self.server.sendmail( self.fromaddr, [toaddr] + cc + bcc, msg.as_string() ) except Exception, e: logging.error( 'Send mail from %s:%s to %s failed: %s' % ( self.host, self.port, toaddr, e) )
def parse_from(self,from_name): nick,email=parseaddr(from_name) if not nick: nick=email if not nick and not email: nick,email = old_parse_from(from_name) return nick,email
def get_or_create_contact(contact_email): name, email = contact_email email=email.lower() names = [] defaults={} if not name: name = extract_name(email) if name: names = name.split(' ') _name, email = parseaddr(email) if len(names) > 1: defaults['first_name'] = names[0] defaults['last_name'] = ' '.join(names[1:]) try: contact = Contact.objects.get(email__iexact=email) except Contact.DoesNotExist: contact = Contact(email=email) contact.save() if not contact.user and User.objects.filter(email__iexact=email).exists(): contact.user = User.objects.filter(email__iexact=email)[0] contact.save() if not contact.first_name and 'first_name' in defaults: contact.first_name = defaults.get('first_name')[:100] if not contact.last_name and 'last_name' in defaults: contact.last_name = defaults.get('last_name')[:100] contact.save() return contact
def fetch_current_subscribers(self): """ Fetch the current list of subscribers by calling out to the majordomo server and scrape the result of the 'who-short' command. """ f = urlopen("https://%s/mj/mj_wwwadm?passw=%s&list=%s&func=who-short" % (self.mjhost, self.listpwd, self.listname)) s = f.read() f.close() # Ugly screen-scraping regexp hack resub = re.compile('list administration<br>\s+</p>\s+<pre>([^<]+)</pre>') m = resub.findall(s) if len(m) != 1: if s.find("<!-- Majordomo who_none format file -->") > 0: # Nobody on the list yet return set() raise Exception("Could not find list of subscribers") # Deal with those HTML entities that are set by the majordomo server. s = m[0].replace('<','<').replace('>','>').replace('"','"') # Parse all email addresses to make sure we can deal with both the # "*****@*****.**" and "Joe User <*****@*****.**>" formats. Return # the address part only, as a unique set. return set([parseaddr(a)[1] for a in re.split('[\r\n]+',s) if a])
def add_members (self, save=False): """Add any email addressses that are provided in the create form.""" if self.welcome == '': text = ('Welcome to %s. Visit the List Server to ' + 'manage your subscriptions') % self.ln else: text = self.welcome for key in self.cgidata.keys(): if re.match('^lc_member_', key): fn, em = parseaddr(self.cgival(key).lower().strip()) userdesc = UserDesc(em, fn, mm_cfg.SSO_STOCK_USER_PWD, False) try: self.ml.ApprovedAddMember(userdesc, True, text, whence='SSO List Creation Time') syslog('sso', 'Successfully added %s to list: %s' % (em, self.ln)) except Errors.MMAlreadyAMember: ## FIXME: Need to find some way of communicating this ## case to the user. As thisi s a new list, this can only ## happen if the same address is given by the admin... hm syslog('sso', '%s already a member of listL %s' % (em, self.ln)) if save: self.ml.Save()
def _decompose_addr(self, addr): addr = addr.replace("[", "").replace("]", "") (name, email) = parseaddr(addr) # The eMail parser cannot handle Surname, Name <*****@*****.**> properly. # Provide a fixup hack for this case if (name == "" or email.count("@") == 0): m = re.search(self.fixup_emailPattern, addr) if m: name = m.group(1) email = m.group(2) m2 = re.search(self.commaNamePattern, name) if m2: # Replace "Surname, Name" by "Name Surname" name = "{0} {1}".format(m2.group(2), m2.group(1)) # print "Fixup for addr {0} required -> ({1}/{2})".format(addr, name, email) else: # In this case, no eMail address was specified. # print("Fixup for email required, but FAILED for {0}".format(addr)) name = addr rand_str = "".join(random.choice(string.ascii_lowercase + string.digits) for i in range(10)) email = "could.not.resolve@"+rand_str email = email.lower() name = self._cleanName(name) email = self._cleanName(email) return (name, email)
def edit_members (self): oldm = self.ml.getRegularMemberKeys() newm = [] for key in self.cgidata.keys(): if re.match('^lc_member_', key): fn, em = parseaddr(self.cgival(key).lower()) newm.append(em) remove = [x for x in oldm if not x in newm] insert = [x for x in newm if not x in oldm] syslog('sso', 'Will remove %s from list %s' % (remove, self.ln)) syslog('sso', 'Will add %s to list %s' % (insert, self.ln)) for em in remove: self.ml.ApprovedDeleteMember(em, whence='SSO Web Interface') for em in insert: userdesc = UserDesc(em, '', mm_cfg.SSO_STOCK_USER_PWD, False) try: self.ml.ApprovedAddMember(userdesc, True, self.welcome, whence='SSO List Edit') syslog('sso', 'Successfully added %s to list: %s' % (em, self.ln)) except Errors.MMAlreadyAMember: syslog('sso', 'request_edit: %s already a member of list %s' % (em, self.ln))
def process(msg): # Yahoo! bounces seem to have a known subject value and something called # an x-uidl: header, the value of which seems unimportant. sender = parseaddr(msg.get('from', '').lower())[1] or '' if not sender.startswith('mailer-daemon@yahoo'): return None addrs = [] # simple state machine # 0 == nothing seen # 1 == tag line seen state = 0 for line in email.Iterators.body_line_iterator(msg): line = line.strip() if state == 0 and tcre.match(line): state = 1 elif state == 1: mo = acre.match(line) if mo: addrs.append(mo.group('addr')) continue mo = ecre.match(line) if mo: # we're at the end of the error response break return addrs
def check_from(self, arguments, all_numeric=re.compile('[0-9][0-9]+$')): assert not arguments, arguments from email.Utils import parseaddr text = self.message.get('From') pair = text and parseaddr(text) if not pair or not pair[1]: self.checker.reject("Missing From.") return text = pair[1].lower() if not text: self.checker.reject("Empty From.") return fields = text.split('@') if len(fields) != 2: self.checker.reject("Invalid From `%s'." % text) return user, domain = fields if all_numeric.match(user): self.checker.reject("All numeric user `%s'." % user) fields = domain.split('.') if len(fields) < 2: self.checker.reject("Domain `%s' not fully qualified." % domain) return if domain == 'hotmail.com': if not self.message.get('X-Originating-IP'): self.checker.reject("Forged From `hotmail.com'.")
def verp_probe(mlist, msg): bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) # Sadly not every MTA bounces VERP messages correctly, or consistently. # Fall back to Delivered-To: (Postfix), Envelope-To: (Exim) and # Apparently-To:, and then short-circuit if we still don't have anything # to work with. Note that there can be multiple Delivered-To: headers so # we need to search them all (and we don't worry about false positives for # forwarded email, because only one should match VERP_REGEXP). vals = [] for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) for field in vals: to = parseaddr(field)[1] if not to: continue # empty header mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to) if not mo: continue # no match of regexp try: if bmailbox <> mo.group('bounces'): continue # not a bounce to our list # Extract the token and see if there's an entry token = mo.group('token') data = mlist.pend_confirm(token, expunge=False) if data is not None: return token except IndexError: syslog( 'error', "VERP_PROBE_REGEXP doesn't yield the right match groups: %s", mm_cfg.VERP_PROBE_REGEXP) return None
def check_blacklists(self, blacklists, parsed_flag=[]): # Extract the From domain. text = self.message.get('From') if text is None: return from email.Utils import parseaddr pair = parseaddr(text) if not pair or not pair[1]: return fields = pair[1].lower().split('@') if len(fields) != 2: return user, domain = fields # Get a list of IP addresses. from . import DNS if not parsed_flag: DNS.ParseResolvConf() parsed_flag.append(None) numeric_ips = [] for answer in DNS.Request(domain, qtype='a').req().answers: numeric_ips.append(answer['data']) if not numeric_ips: if not DNS.Request(domain, qtype='mx').req().answers: self.checker.reject("Domain `%s' is not resolved." % domain) # Validate IP addresses against blacklists. for numeric_ip in numeric_ips: fragments = numeric_ip.split('.') fragments.reverse() for blacklist in blacklists: if '.' not in blacklist: blacklist += '.mail-abuse.org' domain = '.'.join(fragments) + '.' + blacklist if DNS.Request(domain, qtype='a').req().answers: self.checker.reject("Listed in `%s'." % blacklist)
def addUser(self, login, name = None, last_name = None, email = None, passwd = None, sha_passwd = None, note = None): if not email: raise ScalakError("You have to supply user email.") # Some not really nice mailman hacks # Need to import Mailman code sys.path.insert(0, self._mailman_dir) sitedir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'site-packages') sys.path.append(sitedir) from Mailman import mm_cfg from Mailman import MailList from Mailman import Utils from Mailman import Errors from Mailman import Message from Mailman import i18n from email.Utils import parseaddr class UserDesc: pass if not Utils.list_exists(self.listname): raise ScalakError("No such mailing list") mlist = MailList.MailList(self.listname) usr = UserDesc() usr.fullname, usr.address = parseaddr(email) usr.digest = 0 try: mlist.ApprovedAddMember(usr, 0, 0) mlist.Save() finally: mlist.Unlock()
def get_sender(header): replyto = header.get("Reply-To") if replyto is None: replyto = header.get("From") if replyto is None: return None x = parseaddr(replyto) return x[1]
def encode_rfc2822_address_header(header_text): """If ``header_text`` contains non-ASCII characters, attempts to locate patterns of the form ``"Name" <address@domain>`` and replace the ``"Name"`` portion by the RFC2047-encoded version, preserving the address part untouched. """ header_text_utf8 = tools.ustr(header_text).encode('utf-8') header_text_ascii = try_coerce_ascii(header_text_utf8) if header_text_ascii: return header_text_ascii name, email = parseaddr(header_text_utf8) if not name: return email # non-ASCII characters are present, attempt to # replace all "Name" patterns with the RFC2047- # encoded version name_encoded = str(Header(name, 'utf-8')) header_text_utf8 = "%s <%s>" % (name_encoded, email) # try again after encoding header_text_ascii = try_coerce_ascii(header_text_utf8) if header_text_ascii: return header_text_ascii # fallback to extracting pure addresses only, which could # still cause a failure downstream if the actual addresses # contain non-ASCII characters return COMMASPACE.join(extract_rfc2822_addresses(header_text_utf8))
def sender(self): """ get the email address of the sender """ sender = utils.get_header(self.msg(), 'Resent-From') if not sender: sender = utils.get_header(self.msg(), 'From') (sender_name, sender_address) = parseaddr(sender) return sender_address
def create_email(sender, recipient, bcc, subject, body): """Create an email message. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The charset of the email will be the UTF-8. """ header_charset = 'UTF-8' body_charset = 'UTF-8' body.encode(body_charset) # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') bcc_pairs = [] for bcc_mail in bcc: bcc_name, bcc_addr = parseaddr(bcc_mail) bcc_name = str(Header(unicode(bcc_name), header_charset)) bcc_addr = bcc_addr.encode('ascii') bcc_pairs.append((bcc_name, bcc_addr)) # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), 'plain', body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Bcc'] = ', '.join(formataddr((bcc_name, bcc_addr)) for bcc_name, bcc_addr in bcc_pairs) msg['Subject'] = Header(unicode(subject), header_charset) return msg
def get_owners (self): ret = [] for key in self.cgidata.keys(): if re.match('^lc_owner', key): fn, em = parseaddr(self.cgival(key).lower().strip()) ret.append(em) return ret
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 recipients to check for my address rec_to = filter(lambda x: x, mail.get('To', '').split(',')) rec_cc = filter(lambda x: x, mail.get('Cc', '').split(',')) delivered_to = mail.get('Delivered-To', None) recipients = rec_to + rec_cc if delivered_to is not None: recipients.append(delivered_to) logging.debug('recipients: %s' % recipients) # pick the most important account that has an address in recipients # and use that accounts realname and the found recipient address for account in my_accounts: acc_addresses = account.get_addresses() for alias in acc_addresses: if realname is not None: break regex = re.compile(alias) for rec in recipients: seen_name, seen_address = parseaddr(rec) 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 encode_emailaddress(address): """ Encode an email address as ASCII using the Encoded-Word standard. Needed to work around http://code.djangoproject.com/ticket/11144 """ try: return address.encode('ascii') except UnicodeEncodeError: pass nm, addr = parseaddr(address) return formataddr( (str(Header(nm, settings.DEFAULT_CHARSET)), addr) )
def _split_address(address): """Split an username + email address into its parts. This takes "Joe Foo <*****@*****.**>" and returns "Joe Foo", "*****@*****.**". :param address: A combined username :return: (username, email) """ return parseaddr(address)
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 normAddressList(txt, assumedCharset=_7BIT_CHARSET): items = [] if txt: if type(txt) == _UNICODE_TYPE or type(txt) == _STR_TYPE: txt, assumedCharset = safelyEncode(txt, assumedCharset) txt = txt.replace(';', ',') items = [formataddr(parseaddr(x.strip())) for x in txt.split(',') if x.strip()] else: assert(type(txt) == type([])) items = txt newItems = [] for addr in items: txt, assumedCharset = safelyEncode(txt, assumedCharset) newItems.append(formataddr(parseaddr(txt))) items = newItems items = _getUnique(items) items.sort() return items, assumedCharset
def send_email(toaddr, subject, body, cc = [], bcc = []): global email_from, smtp_server, smtp_port, \ email_username, email_pass # TODO: something check if not smtp_server: return False fromaddr = email_from # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break from_name, from_addr = parseaddr(fromaddr) to_name, to_addr = parseaddr(toaddr) from_name = str(Header(unicode(from_name), header_charset)) to_name = str(Header(unicode(to_name), header_charset)) from_addr = from_addr.encode('ascii') to_addr = to_addr.encode('ascii') msg = MIMEText(body.encode(body_charset), 'plain', body_charset) msg['From'] = formataddr((from_name, from_addr)) msg['To'] = formataddr((to_name, to_addr)) if cc: L = [] for x in cc: x_name, x_addr = parseaddr(x) L.append( formataddr((x_name, x_addr)) ) msg['CC'] = ', '.join(L) if bcc: L = [] for x in bcc: x_name, x_addr = parseaddr(x) L.append( formataddr((x_name, x_addr)) ) msg['BCC'] = ', '.join(L) msg['Subject'] = Header(unicode(subject), header_charset) msg['date']=time.strftime('%a, %d %b %Y %H:%M:%S %z') toaddrs = [toaddr] + cc + bcc smtp = SMTP(smtp_server, smtp_port) #smtp.ehlo() if smtp_server.endswith('gmail.com'): smtp.starttls() #smtp.ehlo() if not ( smtp_server.endswith('127.0.0.1') or smtp_server.endswith('localhost') ): smtp.login(email_username, email_pass) smtp.sendmail(fromaddr, toaddrs, msg.as_string()) smtp.quit() return True
def send(sender, recipient, subject, body, contenttype, extraheaders=None, smtpserver=None): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP to localhost port 25. This is easy to change if you want something different. The charset of the email will be the first one out of the list that can represent all the characters occurring in the email. """ # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in CHARSET_LIST: try: body.encode(body_charset) except (UnicodeError, LookupError): pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), contenttype, body_charset) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) for hdr in extraheaders.keys(): try: msg[hdr] = Header(unicode(extraheaders[hdr], header_charset)) except: msg[hdr] = Header(extraheaders[hdr]) fromhdr = formataddr((sender_name, sender_addr)) msg['From'] = fromhdr msg_as_string = msg.as_string() #DEPRECATED if QP_REQUIRED: #DEPRECATED ins, outs = SIO(msg_as_string), SIO() #DEPRECATED mimify.mimify(ins, outs) #DEPRECATED msg_as_string = outs.getvalue() if VERBOSE: print 'Sending:', unu(subject) if SMTP_SEND: if not smtpserver: import smtplib try: if SMTP_SSL: smtpserver = smtplib.SMTP_SSL() else: smtpserver = smtplib.SMTP() smtpserver.connect(SMTP_SERVER) except KeyboardInterrupt: raise except Exception, e: print >> warn, "" print >> warn, ( 'Fatal error: could not connect to mail server "%s"' % SMTP_SERVER) print >> warn, ( 'Check your config.py file to confirm that SMTP_SERVER and other mail server settings are configured properly' ) if hasattr(e, 'reason'): print >> warn, "Reason:", e.reason sys.exit(1) if AUTHREQUIRED: try: smtpserver.ehlo() if not SMTP_SSL: smtpserver.starttls() smtpserver.ehlo() smtpserver.login(SMTP_USER, SMTP_PASS) except KeyboardInterrupt: raise except Exception, e: print >> warn, "" print >> warn, ( 'Fatal error: could not authenticate with mail server "%s" as user "%s"' % (SMTP_SERVER, SMTP_USER)) print >> warn, ( 'Check your config.py file to confirm that SMTP_SERVER and other mail server settings are configured properly' ) if hasattr(e, 'reason'): print >> warn, "Reason:", e.reason sys.exit(1)
print >> outfile, '[<b><a href=".">PEP Index</a></b>]' if pep: try: print >> outfile, ('[<b><a href="pep-%04d.txt">PEP Source</a>' '</b>]' % int(pep)) except ValueError, error: print >> sys.stderr, ('ValueError (invalid PEP number): %s' % error) print >> outfile, '</td></tr></table>' print >> outfile, '<div class="header">\n<table border="0">' for k, v in header: if k.lower() in ('author', 'discussions-to'): mailtos = [] for part in re.split(',\s*', v): if '@' in part: realname, addr = parseaddr(part) if k.lower() == 'discussions-to': m = linkemail(addr, pep) else: m = fixemail(addr, pep) mailtos.append('%s <%s>' % (realname, m)) elif part.startswith('http:'): mailtos.append('<a href="%s">%s</a>' % (part, part)) else: mailtos.append(part) v = COMMASPACE.join(mailtos) elif k.lower() in ('replaces', 'replaced-by', 'requires'): otherpeps = '' for otherpep in re.split(',?\s+', v): otherpep = int(otherpep) otherpeps += '<a href="pep-%04d.html">%i</a> ' % (otherpep,
def fixfile(inpath, input_lines, outfile): try: from email.Utils import parseaddr except ImportError: from email.utils import parseaddr basename = os.path.basename(inpath) infile = iter(input_lines) # convert plaintext pep to minimal XHTML markup print(DTD, file=outfile) print('<html>', file=outfile) print(COMMENT, file=outfile) print('<head>', file=outfile) # head header = [] pep = "" title = "" for line in infile: if not line.strip(): break if line[0].strip(): if ":" not in line: break key, value = line.split(":", 1) value = value.strip() header.append((key, value)) else: # continuation line key, value = header[-1] value = value + line header[-1] = key, value if key.lower() == "title": title = value elif key.lower() == "pep": pep = value if pep: title = "PEP " + pep + " -- " + title if title: print(' <title>%s</title>' % escape(title), file=outfile) r = random.choice(list(range(64))) print((' <link rel="STYLESHEET" href="style.css" type="text/css" />\n' '</head>\n' '<body bgcolor="white">\n' '<table class="navigation" cellpadding="0" cellspacing="0"\n' ' width="100%%" border="0">\n' '<tr><td class="navicon" width="150" height="35">\n' '<a href="../" title="Python Home Page">\n' '<img src="../pics/PyBanner%03d.gif" alt="[Python]"\n' ' border="0" width="150" height="35" /></a></td>\n' '<td class="textlinks" align="left">\n' '[<b><a href="../">Python Home</a></b>]' % r), file=outfile) if basename != 'pep-0000.txt': print('[<b><a href=".">PEP Index</a></b>]', file=outfile) if pep: try: print(('[<b><a href="pep-%04d.txt">PEP Source</a>' '</b>]' % int(pep)), file=outfile) except ValueError as error: print(('ValueError (invalid PEP number): %s' % error), file=sys.stderr) print('</td></tr></table>', file=outfile) print('<div class="header">\n<table border="0">', file=outfile) for k, v in header: if k.lower() in ('author', 'bdfl-delegate', 'discussions-to'): mailtos = [] for part in re.split(',\s*', v): if '@' in part: realname, addr = parseaddr(part) if k.lower() == 'discussions-to': m = linkemail(addr, pep) else: m = fixemail(addr, pep) mailtos.append('%s <%s>' % (realname, m)) elif part.startswith('http:'): mailtos.append('<a href="%s">%s</a>' % (part, part)) else: mailtos.append(part) v = COMMASPACE.join(mailtos) elif k.lower() in ('replaces', 'replaced-by', 'requires'): otherpeps = '' for otherpep in re.split(',?\s+', v): otherpep = int(otherpep) otherpeps += '<a href="pep-%04d.html">%i</a> ' % (otherpep, otherpep) v = otherpeps elif k.lower() in ('last-modified', ): date = v or time.strftime('%d-%b-%Y', time.localtime(os.stat(inpath)[8])) if date.startswith('$' 'Date: ') and date.endswith(' $'): date = date[6:-2] if basename == 'pep-0000.txt': v = date else: try: url = PEPCVSURL % int(pep) v = '<a href="%s">%s</a> ' % (url, escape(date)) except ValueError as error: v = date elif k.lower() in ('content-type', ): url = PEPURL % 9 pep_type = v or 'text/plain' v = '<a href="%s">%s</a> ' % (url, escape(pep_type)) elif k.lower() == 'version': if v.startswith('$' 'Revision: ') and v.endswith(' $'): v = escape(v[11:-2]) else: v = escape(v) print(' <tr><th>%s: </th><td>%s</td></tr>' \ % (escape(k), v), file=outfile) print('</table>', file=outfile) print('</div>', file=outfile) print('<hr />', file=outfile) print('<div class="content">', file=outfile) need_pre = 1 for line in infile: if line[0] == '\f': continue if line.strip() == LOCALVARS: break if line[0].strip(): if not need_pre: print('</pre>', file=outfile) print('<h3>%s</h3>' % line.strip(), file=outfile) need_pre = 1 elif not line.strip() and need_pre: continue else: # PEP 0 has some special treatment if basename == 'pep-0000.txt': parts = line.split() if len(parts) > 1 and re.match(r'\s*\d{1,4}', parts[1]): # This is a PEP summary line, which we need to hyperlink url = PEPURL % int(parts[1]) if need_pre: print('<pre>', file=outfile) need_pre = 0 print(re.sub(parts[1], '<a href="%s">%s</a>' % (url, parts[1]), line, 1), end=' ', file=outfile) continue elif parts and '@' in parts[-1]: # This is a pep email address line, so filter it. url = fixemail(parts[-1], pep) if need_pre: print('<pre>', file=outfile) need_pre = 0 print(re.sub(parts[-1], url, line, 1), end=' ', file=outfile) continue line = fixpat.sub(lambda x, c=inpath: fixanchor(c, x), line) if need_pre: print('<pre>', file=outfile) need_pre = 0 outfile.write(line) if not need_pre: print('</pre>', file=outfile) print('</div>', file=outfile) print('</body>', file=outfile) print('</html>', file=outfile)
def send_email(sender, recipient, subject, body, body_plain="", noreply=False): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP. The charset of the email will be the first one out of US-ASCII, ISO-8859-1 and UTF-8 that can represent all the characters occurring in the email. """ # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message msg = MIMEMultipart('alternative') html_part = MIMEText(body.encode(body_charset), 'html', body_charset) plain_part = MIMEText(body_plain.encode(body_charset), 'plain', body_charset) msg.attach(plain_part) msg.attach(html_part) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) # Send the message via SMTP via one of the configured smtp servers # If all servers fail, throw an error sent = False for smtp_server in tickee.settings.SMTP_SERVERS: try: smtp = SMTP(smtp_server.get('host'), smtp_server.get('port')) smtp.starttls() smtp.login(smtp_server.get('username'), smtp_server.get('password')) smtp.sendmail(sender, recipient, msg.as_string()) smtp.quit() except Exception as e: tlogger.error("failed sending mail to '%s' using smtp '%s'" % (recipient, smtp_server.get('name'))) continue # continue to the next smtp server else: sent = True break # stop trying to send the mail if not sent: raise CoreError('Failed sending email.')
def send_email(self, backend, mail): domain = self.config.get('domain') recipient = self.config.get('recipient') reply_id = '' if mail.parent: reply_id = u'<%s.%s@%s>' % (backend.name, mail.parent.full_id, domain) subject = mail.title sender = u'"%s" <%s@%s>' % (mail.sender.replace('"', '""') if mail.sender else '', backend.name, domain) # assume that .date is an UTC datetime date = formatdate(time.mktime(utc2local(mail.date).timetuple()), localtime=True) msg_id = u'<%s.%s@%s>' % (backend.name, mail.full_id, domain) if self.config.get('html') and mail.flags & mail.IS_HTML: body = mail.content content_type = 'html' else: if mail.flags & mail.IS_HTML: body = html2text(mail.content) else: body = mail.content content_type = 'plain' if body is None: body = '' if mail.signature: if self.config.get('html') and mail.flags & mail.IS_HTML: body += u'<p>-- <br />%s</p>' % mail.signature else: body += u'\n\n-- \n' if mail.flags & mail.IS_HTML: body += html2text(mail.signature) else: body += mail.signature # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), content_type, body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) msg['Message-Id'] = msg_id msg['Date'] = date if reply_id: msg['In-Reply-To'] = reply_id self.logger.info('Send mail from <%s> to <%s>' % (sender, recipient)) if len(self.config.get('pipe')) > 0: p = subprocess.Popen(self.config.get('pipe'), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.stdin.write(msg.as_string()) p.stdin.close() if p.wait() != 0: self.logger.error('Unable to deliver mail: %s' % p.stdout.read().strip()) return False else: # Send the message via SMTP to localhost:25 try: smtp = SMTP(self.config.get('smtp')) smtp.sendmail(sender, recipient, msg.as_string()) except Exception as e: self.logger.error('Unable to deliver mail: %s' % e) return False else: smtp.quit() return True
def fixfile(inpath, input_lines, outfile): from email.Utils import parseaddr basename = os.path.basename(inpath) infile = iter(input_lines) # convert plaintext psep to minimal XHTML markup print >> outfile, DTD print >> outfile, '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">' print >> outfile, COMMENT print >> outfile, '<head>' # head header = [] psep = "" title = "" for line in infile: if not line.strip(): break if line[0].strip(): if ":" not in line: break key, value = line.split(":", 1) value = value.strip() header.append((key, value)) else: # continuation line key, value = header[-1] value = value + line header[-1] = key, value if key.lower() == "title": title = value elif key.lower() == "psep": psep = value if psep: title = "PSEP " + psep + " -- " + title if title: print >> outfile, ' <title>%s</title>' % cgi.escape(title) r = random.choice(range(64)) print >> outfile, ( ' <link rel="STYLESHEET" href="psep.css" type="text/css" />\n' '</head>\n' '<body>\n' '<div id="header">\n' '<a href="http://www.pyside.org/" title="Python Home Page">\n' '<img src="http://www.pyside.org/wp-content/themes/openbossa/images/logo.png" alt="[PySide]"\n' 'border="0" width="199" height="102" /></a>\n' '[<b><a href="http://www.pyside.org/">PySide Home</a></b>]\n' '</div>\n' '<div id="content">\n') if basename <> 'psep-0000.txt': print >> outfile, '[<b><a href=".">PSEP Index</a></b>]' print >> outfile, '<table border="0">' for k, v in header: if k.lower() in ('author', 'discussions-to'): mailtos = [] for part in re.split(',\s*', v): if '@' in part: realname, addr = parseaddr(part) if k.lower() == 'discussions-to': m = linkemail(addr, psep) else: m = fixemail(addr, psep) mailtos.append('%s <%s>' % (realname, m)) elif part.startswith('http:'): mailtos.append( '<a href="%s">%s</a>' % (part, part)) else: mailtos.append(part) v = COMMASPACE.join(mailtos) elif k.lower() in ('replaces', 'replaced-by', 'requires'): otherpseps = '' for otherpsep in re.split(',?\s+', v): otherpsep = int(otherpsep) otherpseps += '<a href="psep-%04d.html">%i</a> ' % (otherpsep, otherpsep) v = otherpseps elif k.lower() in ('last-modified',): date = time.strftime('%d-%b-%Y', time.localtime(os.stat(inpath)[8])) if int(psep) != 0: url = PSEPCVSURL % int(psep) v = '<a href="%s">%s</a> ' % (url, cgi.escape(date)) else: v = date elif k.lower() in ('content-type',): url = PSEPURL % 9 psep_type = v or 'text/plain' v = '<a href="%s">%s</a> ' % (url, cgi.escape(psep_type)) else: v = cgi.escape(v) print >> outfile, ' <tr class="field"><th class="field-name">%s: </th><td class="field-body">%s</td></tr>' \ % (cgi.escape(k), v) print >> outfile, '</table>' print >> outfile, '<hr />' need_pre = 1 for line in infile: if line[0] == '\f': continue if line.strip() == LOCALVARS: break if line[0].strip(): if not need_pre: print >> outfile, '</pre>' print >> outfile, '<h3>%s</h3>' % line.strip() need_pre = 1 elif not line.strip() and need_pre: continue else: # PSEP 0 has some special treatment if basename == 'psep-0000.txt': parts = line.split() if len(parts) > 1 and re.match(r'\s*\d{1,4}', parts[1]): # This is a PSEP summary line, which we need to hyperlink url = PSEPURL % int(parts[1]) if need_pre: print >> outfile, '<pre>' need_pre = 0 print >> outfile, re.sub( parts[1], '<a href="%s">%s</a>' % (url, parts[1]), line, 1), continue elif parts and '@' in parts[-1]: # This is a psep email address line, so filter it. url = fixemail(parts[-1], psep) if need_pre: print >> outfile, '<pre>' need_pre = 0 print >> outfile, re.sub( parts[-1], url, line, 1), continue line = fixpat.sub(lambda x, c=inpath: fixanchor(c, x), line) if need_pre: print >> outfile, '<pre>' need_pre = 0 outfile.write(line) if not need_pre: print >> outfile, '</pre>' print >> outfile, '</div>' print >> outfile, '</body>' print >> outfile, '</html>'
def getBugReporter(self, remote_bug): """See ISupportsBugImport.""" debian_bug = self._findBug(remote_bug) reporter_name, reporter_email = parseaddr(debian_bug.originator) return reporter_name, reporter_email
def parse(self, m, prefix=None): """Parse mail sent by FreshCVS""" # FreshCVS sets From: to "user CVS <user>", but the <> part may be # modified by the MTA (to include a local domain) name, addr = parseaddr(m["from"]) if not name: return None # no From means this message isn't from FreshCVS cvs = name.find(" CVS") if cvs == -1: return None # this message isn't from FreshCVS who = name[:cvs] # we take the time of receipt as the time of checkin. Not correct, # but it avoids the out-of-order-changes issue. See the comment in # parseSyncmail about using the 'Date:' header when = util.now() files = [] comments = "" isdir = 0 lines = list(body_line_iterator(m)) while lines: line = lines.pop(0) if line == "Modified files:\n": break while lines: line = lines.pop(0) if line == "\n": break line = line.rstrip("\n") linebits = line.split(None, 1) file = linebits[0] if prefix: # insist that the file start with the prefix: FreshCVS sends # changes we don't care about too if file.startswith(prefix): file = file[len(prefix):] else: continue if len(linebits) == 1: isdir = 1 elif linebits[1] == "0 0": isdir = 1 files.append(file) while lines: line = lines.pop(0) if line == "Log message:\n": break # message is terminated by "ViewCVS links:" or "Index:..." (patch) while lines: line = lines.pop(0) if line == "ViewCVS links:\n": break if line.find("Index: ") == 0: break comments += line comments = comments.rstrip() + "\n" if not files: return None change = changes.Change(who, files, comments, isdir, when=when) return change
def parse(self, m, prefix=None): """Parse messages sent by the 'buildbot-cvs-mail' program. """ # The mail is sent from the person doing the checkin. Assume that the # local username is enough to identify them (this assumes a one-server # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS # model) name, addr = parseaddr(m["from"]) if not addr: return None # no From means this message isn't from buildbot-cvs-mail at = addr.find("@") if at == -1: author = addr # might still be useful else: author = addr[:at] # CVS accecpts RFC822 dates. buildbot-cvs-mail adds the date as # part of the mail header, so use that. # This assumes cvs is being access via ssh or pserver, so the time # will be the CVS server's time. # calculate a "revision" based on that timestamp, or the current time # if we're unable to parse the date. log.msg('Processing CVS mail') dateTuple = parsedate_tz(m["date"]) if dateTuple == None: when = util.now() else: when = mktime_tz(dateTuple) theTime = datetime.datetime.utcfromtimestamp(float(when)) rev = theTime.strftime('%Y-%m-%d %H:%M:%S') catRE = re.compile('^Category:\s*(\S.*)') cvsRE = re.compile('^CVSROOT:\s*(\S.*)') cvsmodeRE = re.compile('^Cvsmode:\s*(\S.*)') filesRE = re.compile('^Files:\s*(\S.*)') modRE = re.compile('^Module:\s*(\S.*)') pathRE = re.compile('^Path:\s*(\S.*)') projRE = re.compile('^Project:\s*(\S.*)') singleFileRE = re.compile('(.*) (NONE|\d(\.|\d)+) (NONE|\d(\.|\d)+)') tagRE = re.compile('^\s+Tag:\s*(\S.*)') updateRE = re.compile('^Update of:\s*(\S.*)') comments = "" branch = None cvsroot = None fileList = None files = [] isdir = 0 path = None project = None lines = list(body_line_iterator(m)) while lines: line = lines.pop(0) m = catRE.match(line) if m: category = m.group(1) continue m = cvsRE.match(line) if m: cvsroot = m.group(1) continue m = cvsmodeRE.match(line) if m: cvsmode = m.group(1) continue m = filesRE.match(line) if m: fileList = m.group(1) continue m = modRE.match(line) if m: # We don't actually use this #module = m.group(1) continue m = pathRE.match(line) if m: path = m.group(1) continue m = projRE.match(line) if m: project = m.group(1) continue m = tagRE.match(line) if m: branch = m.group(1) continue m = updateRE.match(line) if m: # We don't actually use this #updateof = m.group(1) continue if line == "Log Message:\n": break # CVS 1.11 lists files as: # repo/path file,old-version,new-version file2,old-version,new-version # Version 1.12 lists files as: # file1 old-version new-version file2 old-version new-version # # files consists of tuples of 'file-name old-version new-version' # The versions are either dotted-decimal version numbers, ie 1.1 # or NONE. New files are of the form 'NONE NUMBER', while removed # files are 'NUMBER NONE'. 'NONE' is a literal string # Parsing this instead of files list in 'Added File:' etc # makes it possible to handle files with embedded spaces, though # it could fail if the filename was 'bad 1.1 1.2' # For cvs version 1.11, we expect # my_module new_file.c,NONE,1.1 # my_module removed.txt,1.2,NONE # my_module modified_file.c,1.1,1.2 # While cvs version 1.12 gives us # new_file.c NONE 1.1 # removed.txt 1.2 NONE # modified_file.c 1.1,1.2 if fileList is None: log.msg('CVSMaildirSource Mail with no files. Ignoring') return None # We don't have any files. Email not from CVS if cvsmode == '1.11': # Please, no repo paths with spaces! m = re.search('([^ ]*) ', fileList) if m: path = m.group(1) else: log.msg( 'CVSMaildirSource can\'t get path from file list. Ignoring mail' ) return fileList = fileList[len(path):].strip() singleFileRE = re.compile( '(.+?),(NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+)),(NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+))(?: |$)' ) elif cvsmode == '1.12': singleFileRE = re.compile( '(.+?) (NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+)) (NONE|(?:\d+\.(?:\d+\.\d+\.)*\d+))(?: |$)' ) if path is None: raise ValueError( 'CVSMaildirSource cvs 1.12 require path. Check cvs loginfo config' ) else: raise ValueError('Expected cvsmode 1.11 or 1.12. got: %s' % cvsmode) log.msg("CVSMaildirSource processing filelist: %s" % fileList) while (fileList): m = singleFileRE.match(fileList) if m: curFile = path + '/' + m.group(1) files.append(curFile) fileList = fileList[m.end():] else: log.msg('CVSMaildirSource no files matched regex. Ignoring') return None # bail - we couldn't parse the files that changed # Now get comments while lines: line = lines.pop(0) comments += line comments = comments.rstrip() + "\n" if comments == '\n': comments = None return ('cvs', dict(author=author, files=files, comments=comments, isdir=isdir, when=when, branch=branch, revision=rev, category=category, repository=cvsroot, project=project, properties=self.properties))
def parse(self, m, prefix=None): """Parse messages sent by the svn 'commit-email.pl' trigger. """ # The mail is sent from the person doing the checkin. Assume that the # local username is enough to identify them (this assumes a one-server # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS # model) name, addr = parseaddr(m["from"]) if not addr: return None # no From means this message isn't from svn at = addr.find("@") if at == -1: author = addr # might still be useful else: author = addr[:at] # we take the time of receipt as the time of checkin. Not correct (it # depends upon the email latency), but it avoids the # out-of-order-changes issue. Also syncmail doesn't give us anything # better to work with, unless you count pulling the v1-vs-v2 # timestamp out of the diffs, which would be ugly. TODO: Pulling the # 'Date:' header from the mail is a possibility, and # email.Utils.parsedate_tz may be useful. It should be configurable, # however, because there are a lot of broken clocks out there. when = util.now() files = [] comments = "" lines = list(body_line_iterator(m)) rev = None while lines: line = lines.pop(0) # "Author: jmason" match = re.search(r"^Author: (\S+)", line) if match: author = match.group(1) # "New Revision: 105955" match = re.search(r"^New Revision: (\d+)", line) if match: rev = match.group(1) # possible TODO: use "Date: ..." data here instead of time of # commit message receipt, above. however, this timestamp is # specified *without* a timezone, in the server's local TZ, so to # be accurate buildbot would need a config setting to specify the # source server's expected TZ setting! messy. # this stanza ends with the "Log:" if (line == "Log:\n"): break # commit message is terminated by the file-listing section while lines: line = lines.pop(0) if (line == "Modified:\n" or line == "Added:\n" or line == "Removed:\n"): break comments += line comments = comments.rstrip() + "\n" while lines: line = lines.pop(0) if line == "\n": break if line.find("Modified:\n") == 0: continue # ignore this line if line.find("Added:\n") == 0: continue # ignore this line if line.find("Removed:\n") == 0: continue # ignore this line line = line.strip() thesefiles = line.split(" ") for f in thesefiles: if prefix: # insist that the file start with the prefix: we may get # changes we don't care about too if f.startswith(prefix): f = f[len(prefix):] else: log.msg("ignored file from svn commit: prefix '%s' " "does not match filename '%s'" % (prefix, f)) continue # TODO: figure out how new directories are described, set # .isdir files.append(f) if not files: log.msg("no matching files found, ignoring commit") return None return ('svn', dict(author=author, files=files, comments=comments, when=when, revision=rev))
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 getRawAddress(address): name, addr = parseaddr(address) return addr
def ticket_from_message(message, queue, quiet): # 'message' must be an RFC822 formatted message. msg = message message = email.message_from_string(msg) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = parseaddr(sender)[1] body_plain, body_html = '', '' for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') else: ticket = None counter = 0 files = [] 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 = EmailReplyParser.parse_reply(decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))) else: body_html = part.get_payload(decode=True) try: # strip html tags body_plain = striptags(body_html) except DjangoUnicodeDecodeError as e: charset = chardet.detect(body_html)['encoding'] body_plain = striptags(unicode(body_html, charset)) # remove extra new lines body_plain, n = re.subn(r'[\r\n]+', r'\n', body_plain) # remove extra spaces body_plain, n = re.subn(r'\s+$', '', body_plain, flags=re.M) body_plain = unescape(body_plain) 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 if body_plain: body = body_plain if body_html: body += '\n\n' body += _('***Note that HTML tags are stripped out. Please see attachment email_html_body.html for the full html content.') else: body = _('No plain-text email body available. Please see attachment email_html_body.html.') if body_html: files.append({ 'filename': _("email_html_body.html"), 'content': body_html, 'type': 'text/html', }) now = timezone.now() if ticket: try: t = Ticket.objects.get(id=ticket) new = False except Ticket.DoesNotExist: ticket = None priority = 3 smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = ('high', 'important', '1', 'urgent') if smtp_priority in high_priority_types or smtp_importance in high_priority_types: priority = 2 if ticket == None: t = Ticket( title=subject, queue=queue, submitter_email=sender_email, created=now, description=body, priority=priority, ) t.save() new = True update = '' elif t.status == Ticket.CLOSED_STATUS: t.status = Ticket.REOPENED_STATUS t.save() f = FollowUp( ticket = t, title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}), date = timezone.now(), public = True, comment = body, ) if t.status == Ticket.REOPENED_STATUS: f.new_status = Ticket.REOPENED_STATUS f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}) f.save() if not quiet: print (" [%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace') for file in files: if file['content']: filename = file['filename'].encode('ascii', 'replace').replace(' ', '_') filename = re.sub('[^a-zA-Z0-9._-]+', '', filename) a = Attachment( followup=f, filename=filename, mime_type=file['type'], size=len(file['content']), ) a.file.save(filename, ContentFile(file['content']), save=False) a.save() if not quiet: print " - %s" % filename context = safe_template_context(t) if new: if sender_email: send_templated_mail( 'newticket_submitter', context, recipients=sender_email, sender=queue.from_address, fail_silently=True, ) if queue.new_ticket_cc: send_templated_mail( 'newticket_cc', context, recipients=queue.new_ticket_cc, sender=queue.from_address, fail_silently=True, ) if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc: send_templated_mail( 'newticket_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, ) else: context.update(comment=f.comment) if t.status == Ticket.REOPENED_STATUS: update = _(' (Reopened)') else: update = _(' (Updated)') if t.assigned_to: send_templated_mail( 'updated_owner', context, recipients=t.assigned_to.email, sender=queue.from_address, fail_silently=True, ) if queue.updated_ticket_cc: send_templated_mail( 'updated_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, ) return t
def _formataddr(self, addr): x_name, x_addr = parseaddr( addr ) x_name = str(Header(unicode(x_name), self.header_charset)) x_addr.encode('ascii') return formataddr((x_name, x_addr))
def send_email(sender, cc_recipients, bcc_recipients, subject, body, attachments=None): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP to 172.17.0.1. This is easy to change if you want something different. The charset of the email will be the first one out of US-ASCII, ISO-8859-1 and UTF-8 that can represent all the characters occurring in the email. """ # combined recipients recipients = cc_recipients + bcc_recipients # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) parsed_cc_recipients = [parseaddr(rec) for rec in cc_recipients] parsed_bcc_recipients = [parseaddr(rec) for rec in bcc_recipients] #recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(str(sender_name), header_charset)) unicode_parsed_cc_recipients = [] for recipient_name, recipient_addr in parsed_cc_recipients: recipient_name = str(Header(str(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters recipient_addr = recipient_addr.encode('ascii') unicode_parsed_cc_recipients.append((recipient_name, recipient_addr)) unicode_parsed_bcc_recipients = [] for recipient_name, recipient_addr in parsed_bcc_recipients: recipient_name = str(Header(str(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters recipient_addr = recipient_addr.encode('ascii') unicode_parsed_bcc_recipients.append((recipient_name, recipient_addr)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEMultipart() msg['CC'] = COMMASPACE.join([ formataddr((recipient_name, recipient_addr)) for recipient_name, recipient_addr in unicode_parsed_cc_recipients ]) msg['BCC'] = COMMASPACE.join([ formataddr((recipient_name, recipient_addr)) for recipient_name, recipient_addr in unicode_parsed_bcc_recipients ]) msg['Subject'] = Header(str(subject), header_charset) msg['FROM'] = "*****@*****.**" msg.attach(MIMEText(body.encode(body_charset), 'plain', body_charset)) # Add attachments if isinstance(attachments, dict): for fname in attachments: part = MIMEBase('application', "octet-stream") part.set_payload(attachments[fname]) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % fname) msg.attach(part) # Send the message via SMTP to docker host smtp_url = "smtp://127.0.0.1:25" logger.info("smtp_url : %s" % smtp_url) smtp = SMTP("127.0.0.1") smtp.sendmail(sender, recipients, msg.as_string()) smtp.quit()
def parse(self, m, prefix): """parse email sent by cradek's EMC CVS Script 1.0""" #log.msg("got an email!") #log.msg(m) # the From: should look like this: EMC CVS server <*****@*****.**> name, addr = parseaddr(m["from"]); expected_name = "EMC CVS server" expected_addr = "*****@*****.**" if name != expected_name: log.msg("email is from \"%s\", expected \"%s\"" % (name, expected_name)) return None if addr != expected_addr: log.msg("email is from \"%s\", expected \"%s\"" % (addr, expected_addr)) return None # we take the time of receipt as the time of checkin. Not correct, # but it avoids the out-of-order-changes issue. See the comment in # parseSyncmail about using the 'Date:' header when = util.now() # # the commit notification email looks like this: # # <message> # <generator> # <name>EMC CIA script</name> # <version>1.0</version> # </generator> # <source> # <project>EMC</project> # <module>emc2/docs/src/gui</module> # <branch>TRUNK</branch> # </source> # <body> # <commit> # <files><file>axis.lyx</file></files> # <author>bigjohnt</author> # <log>minor edit # </log> # </commit> # </body> # </message> files = [] module = "" branch = "" comments = "" author = "" isdir = 0 lines = list(body_line_iterator(m)) while lines: line = lines.pop(0) #log.msg("thinking about line \"%s\"" % line) if re.search(r"\<module\>emc2", line): module = "emc2" continue match = re.search(r"\<branch\>([^<]+)", line) if match: branch = match.group(1) continue match = re.search(r"\<author\>([^\<]+)\<", line) if match: author = match.group(1) continue match = re.search(r"\<files\>(.+)\<\/files\>", line) if match: f = match.group(1) # f begins and ends with the separator, so the split result begins and ends with an empty string new_files = re.split(r"(?:</?file>)+", f) while new_files: new_file = new_files.pop(0) if new_file: files.append(new_file) continue match = re.search(r"\<log\>(.*)", line) if match: comments += match.group(1) while lines: line = lines.pop(0) if re.search(r"\<\/log\>", line): break comments += line continue if module != "emc2": log.msg("email was not for the emc2 module, ignoring"); return None if not branch: log.msg("email contained no branch"); return None if not files: log.msg("email contained no files"); return None if not author: log.msg("email contained no author"); return None log.msg("accepting change from email:"); log.msg(" branch = %s" % branch); log.msg(" author = %s" % author); log.msg(" files = %s" % files); log.msg(" comments = %s" % comments); # the "branch" value gets used by Buildbot as a CVS Tag, and for TRUNK the proper Tag is None if branch == "TRUNK": branch = None return changes.Change(author, files, comments, when=when, branch=branch)
mail_to, repr(ex), str(ex)) logger(errstr) pass else: # could not use smtplib interface on linux without authorization # use sendmail interface tmp_file = None tmp_file_attach = None try: if mswindows: cmd = [ os.path.join(plesk_config.get('PRODUCT_ROOT_D'), "admin", "bin", "plesk_sendmail.exe") ] cmd.append("--send") (mail_from_name, mail_from_addr) = parseaddr(mail_from) cmd.append("--from=" + str(Header(mail_from_addr))) cmd.append("--from-name=" + str(Header(mail_from_name))) cmd.append("--to=" + str(Header(mail_to))) cmd.append("--to-name=" + str(Header(mail_to))) if self.__reply_to: (reply_to_name, reply_to_addr) = parseaddr(self.__reply_to) cmd.append("--reply-to=" + str(Header(reply_to_addr))) cmd.append("--subject=" + str(Header(subject))) tmp_file = os.path.join( pmm_config.tmp_directory(), "pmmcli_daemon_mail_" + str(random.randint(0, 1000))) with codecs.open(tmp_file, 'w', encoding='utf-8') as f: f.write(body) logger("tmp_file %s" % tmp_file)
def getActualAddr(addr): # "Foo Bar <*****@*****.**>" -> "*****@*****.**" return parseaddr(addr)[1].lower()
def safe_mailaddr(addr): name, addr = parseaddr(addr) return formataddr((str(Header(unicode(name), header_charset)), addr.encode('ascii')))
def process(res, args): mlist = res.mlist address = None password = None ok = False full = False if mlist.private_roster == 0: # Public rosters if args: if len(args) == 1: if mlist.Authenticate((mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin), args[0]): full = True else: usage(res) return STOP else: usage(res) return STOP ok = True elif mlist.private_roster == 1: # List members only if len(args) == 1: password = args[0] realname, address = parseaddr(res.msg['from']) elif len(args) == 2 and args[1].startswith('address='): password = args[0] address = args[1][8:] else: usage(res) return STOP if mlist.isMember(address) and mlist.Authenticate( (mm_cfg.AuthUser, mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin), password, address): # Then ok = True if mlist.Authenticate( (mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin), password): # Then ok = full = True else: # Admin only if len(args) <> 1: usage(res) return STOP if mlist.Authenticate((mm_cfg.AuthListModerator, mm_cfg.AuthListAdmin), args[0]): ok = full = True if not ok: res.results.append( _('You are not allowed to retrieve the list membership.')) return STOP # It's okay for this person to see the list membership dmembers = mlist.getDigestMemberKeys() rmembers = mlist.getRegularMemberKeys() if not dmembers and not rmembers: res.results.append(_('This list has no members.')) return # Convenience function def addmembers(members): for member in members: if not full and mlist.getMemberOption(member, mm_cfg.ConcealSubscription): continue realname = mlist.getMemberName(member) if realname: res.results.append(' %s (%s)' % (member, realname)) else: res.results.append(' %s' % member) if rmembers: res.results.append(_('Non-digest (regular) members:')) addmembers(rmembers) if dmembers: res.results.append(_('Digest members:')) addmembers(dmembers)
def send(sender, recipient, subject, body, contenttype, datetime, extraheaders=None, mailserver=None, folder=None): """Send an email. All arguments should be Unicode strings (plain ASCII works as well). Only the real name part of sender and recipient addresses may contain non-ASCII characters. The email will be properly MIME encoded and delivered though SMTP to localhost port 25. This is easy to change if you want something different. The charset of the email will be the first one out of the list that can represent all the characters occurring in the email. """ # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in CHARSET_LIST: try: body.encode(body_charset) except (UnicodeError, LookupError): pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), contenttype, body_charset) if IMAP_OVERRIDE_TO: msg['To'] = IMAP_OVERRIDE_TO else: msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) for hdr in extraheaders.keys(): try: msg[hdr] = Header(unicode(extraheaders[hdr], header_charset)) except: msg[hdr] = Header(extraheaders[hdr]) fromhdr = formataddr((sender_name, sender_addr)) if IMAP_MUNGE_FROM: msg['From'] = extraheaders['X-MUNGED-FROM'] else: msg['From'] = fromhdr msg_as_string = msg.as_string() if IMAP_SEND: if not mailserver: try: (host,port) = IMAP_SERVER.split(':',1) except ValueError: host = IMAP_SERVER port = 993 if IMAP_SSL else 143 try: if IMAP_SSL: mailserver = imaplib.IMAP4_SSL(host, port) else: mailserver = imaplib.IMAP4(host, port) # speed up interactions on TCP connections using small packets mailserver.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) mailserver.login(IMAP_USER, IMAP_PASS) except KeyboardInterrupt: raise except Exception, e: print >>warn, "" print >>warn, ('Fatal error: could not connect to mail server "%s"' % IMAP_SERVER) print >>warn, ('Check your config.py file to confirm that IMAP_SERVER and other mail server settings are configured properly') if hasattr(e, 'reason'): print >>warn, "Reason:", e.reason sys.exit(1) if not folder: folder = DEFAULT_IMAP_FOLDER #mailserver.debug = 4 if mailserver.select(folder)[0] == 'NO': print >>warn, ("%s does not exist, creating" % folder) mailserver.create(folder) mailserver.subscribe(folder) mailserver.append(folder,'',imaplib.Time2Internaldate(datetime), msg_as_string) return mailserver
def process(res, args): mlist = res.mlist digest = None password = None address = None realname = None # Parse the args argnum = 0 for arg in args: if arg.lower().startswith('address='): address = arg[8:] elif argnum == 0: password = arg elif argnum == 1: if arg.lower() not in ('digest', 'nodigest'): res.results.append(_('Bad digest specifier: %(arg)s')) return STOP if arg.lower() == 'digest': digest = 1 else: digest = 0 else: res.results.append(_('Usage:')) res.results.append(gethelp(mlist)) return STOP argnum += 1 # Fix the password/digest issue if (digest is None and password and password.lower() in ('digest', 'nodigest')): if password.lower() == 'digest': digest = 1 else: digest = 0 password = None # Fill in empty defaults if digest is None: digest = mlist.digest_is_default if password is None: password = Utils.MakeRandomPassword() if address is None: realname, address = parseaddr(res.msg['from']) if not address: # Fall back to the sender address address = res.msg.get_sender() if not address: res.results.append(_('No valid address found to subscribe')) return STOP # Watch for encoded names try: h = make_header(decode_header(realname)) # BAW: in Python 2.2, use just unicode(h) realname = h.__unicode__() except UnicodeError: realname = u'' # Coerce to byte string if uh contains only ascii try: realname = realname.encode('us-ascii') except UnicodeError: pass # Create the UserDesc record and do a non-approved subscription listowner = mlist.GetOwnerEmail() userdesc = UserDesc(address, realname, password, digest) remote = res.msg.get_sender() try: mlist.AddMember(userdesc, remote) except Errors.MembershipIsBanned: res.results.append( _("""\ The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please contact the list owners at %(listowner)s.""")) return STOP except Errors.MMBadEmailError: res.results.append( _("""\ Mailman won't accept the given email address as a valid address. (E.g. it must have an @ in it.)""")) return STOP except Errors.MMHostileAddress: res.results.append( _("""\ Your subscription is not allowed because the email address you gave is insecure.""")) return STOP except Errors.MMAlreadyAMember: res.results.append(_('You are already subscribed!')) return STOP except Errors.MMCantDigestError: res.results.append( _('No one can subscribe to the digest of this list!')) return STOP except Errors.MMMustDigestError: res.results.append(_('This list only supports digest subscriptions!')) return STOP except Errors.MMSubscribeNeedsConfirmation: # We don't need to respond /and/ send a confirmation message. res.respond = 0 except Errors.MMNeedApproval: res.results.append( _("""\ Your subscription request has been forwarded to the list administrator at %(listowner)s for review.""")) else: # Everything is a-ok res.results.append(_('Subscription request succeeded.'))
def parse(self, m, prefix=None): """Parse messages sent by the 'syncmail' program, as suggested by the sourceforge.net CVS Admin documentation. Syncmail is maintained at syncmail.sf.net . """ # pretty much the same as freshcvs mail, not surprising since CVS is # the one creating most of the text # The mail is sent from the person doing the checkin. Assume that the # local username is enough to identify them (this assumes a one-server # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS # model) name, addr = parseaddr(m["from"]) if not addr: return None # no From means this message isn't from FreshCVS at = addr.find("@") if at == -1: who = addr # might still be useful else: who = addr[:at] # we take the time of receipt as the time of checkin. Not correct (it # depends upon the email latency), but it avoids the # out-of-order-changes issue. Also syncmail doesn't give us anything # better to work with, unless you count pulling the v1-vs-v2 # timestamp out of the diffs, which would be ugly. TODO: Pulling the # 'Date:' header from the mail is a possibility, and # email.Utils.parsedate_tz may be useful. It should be configurable, # however, because there are a lot of broken clocks out there. when = util.now() subject = m["subject"] # syncmail puts the repository-relative directory in the subject: # mprefix + "%(dir)s %(file)s,%(oldversion)s,%(newversion)s", where # 'mprefix' is something that could be added by a mailing list # manager. # this is the only reasonable way to determine the directory name space = subject.find(" ") if space != -1: directory = subject[:space] else: directory = subject files = [] comments = "" isdir = 0 branch = None lines = list(body_line_iterator(m)) while lines: line = lines.pop(0) if (line == "Modified Files:\n" or line == "Added Files:\n" or line == "Removed Files:\n"): break while lines: line = lines.pop(0) if line == "\n": break if line == "Log Message:\n": lines.insert(0, line) break line = line.lstrip() line = line.rstrip() # note: syncmail will send one email per directory involved in a # commit, with multiple files if they were in the same directory. # Unlike freshCVS, it makes no attempt to collect all related # commits into a single message. # note: syncmail will report a Tag underneath the ... Files: line # e.g.: Tag: BRANCH-DEVEL if line.startswith('Tag:'): branch = line.split(' ')[-1].rstrip() continue thesefiles = line.split(" ") for f in thesefiles: f = directory + "/" + f if prefix: # insist that the file start with the prefix: we may get # changes we don't care about too if f.startswith(prefix): f = f[len(prefix):] else: continue break # TODO: figure out how new directories are described, set # .isdir files.append(f) if not files: return None while lines: line = lines.pop(0) if line == "Log Message:\n": break # message is terminated by "Index:..." (patch) or "--- NEW FILE.." # or "--- filename DELETED ---". Sigh. while lines: line = lines.pop(0) if line.find("Index: ") == 0: break if re.search(r"^--- NEW FILE", line): break if re.search(r" DELETED ---$", line): break comments += line comments = comments.rstrip() + "\n" change = changes.Change(who, files, comments, isdir, when=when, branch=branch) return change
def process(res, args): mlist = res.mlist address = None if not args: # They just want to get their existing password realname, address = parseaddr(res.msg['from']) if mlist.isMember(address): password = mlist.getMemberPassword(address) res.results.append(_('Your password is: %(password)s')) # Prohibit multiple password retrievals. return STOP else: listname = mlist.real_name res.results.append( _('You are not a member of the %(listname)s mailing list')) return STOP elif len(args) == 1 and args[0].startswith('address='): # They want their password, but they're posting from a different # address. We /must/ return the password to the subscribed address. address = args[0][8:] res.returnaddr = address if mlist.isMember(address): password = mlist.getMemberPassword(address) res.results.append(_('Your password is: %(password)s')) # Prohibit multiple password retrievals. return STOP else: listname = mlist.real_name res.results.append( _('You are not a member of the %(listname)s mailing list')) return STOP elif len(args) == 2: # They are changing their password oldpasswd = args[0] newpasswd = args[1] realname, address = parseaddr(res.msg['from']) if mlist.isMember(address): if mlist.Authenticate((mm_cfg.AuthUser, mm_cfg.AuthListAdmin), oldpasswd, address): mlist.setMemberPassword(address, newpasswd) res.results.append(_('Password successfully changed.')) else: res.results.append( _("""\ You did not give the correct old password, so your password has not been changed. Use the no argument version of the password command to retrieve your current password, then try again.""")) res.results.append(_('\nUsage:')) res.results.append(gethelp(mlist)) return STOP else: listname = mlist.real_name res.results.append( _('You are not a member of the %(listname)s mailing list')) return STOP elif len(args) == 3 and args[2].startswith('address='): # They want to change their password, and they're sending this from a # different address than what they're subscribed with. Be sure the # response goes to the subscribed address. oldpasswd = args[0] newpasswd = args[1] address = args[2][8:] res.returnaddr = address if mlist.isMember(address): if mlist.Authenticate((mm_cfg.AuthUser, mm_cfg.AuthListAdmin), oldpasswd, address): mlist.setMemberPassword(address, newpasswd) res.results.append(_('Password successfully changed.')) else: res.results.append( _("""\ You did not give the correct old password, so your password has not been changed. Use the no argument version of the password command to retrieve your current password, then try again.""")) res.results.append(_('\nUsage:')) res.results.append(gethelp(mlist)) return STOP else: listname = mlist.real_name res.results.append( _('You are not a member of the %(listname)s mailing list')) return STOP
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))
class MaildirRunner(Runner): # This class is much different than most runners because it pulls files # of a different format than what scripts/post and friends leaves. The # files this runner reads are just single message files as dropped into # the directory by the MTA. This runner will read the file, and enqueue # it in the expected qfiles directory for normal processing. def __init__(self, slice=None, numslices=1): # Don't call the base class constructor, but build enough of the # underlying attributes to use the base class's implementation. self._stop = 0 self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') self._parser = Parser(Message) def _oneloop(self): # Refresh this each time through the list. BAW: could be too # expensive. listnames = Utils.list_names() # Cruise through all the files currently in the new/ directory try: files = os.listdir(self._dir) except OSError, e: if e.errno <> errno.ENOENT: raise # Nothing's been delivered yet return 0 for file in files: srcname = os.path.join(self._dir, file) dstname = os.path.join(self._cur, file + ':1,P') xdstname = os.path.join(self._cur, file + ':1,X') try: os.rename(srcname, dstname) except OSError, e: if e.errno == errno.ENOENT: # Some other MaildirRunner beat us to it continue syslog('error', 'Could not rename maildir file: %s', srcname) raise # Now open, read, parse, and enqueue this message try: fp = open(dstname) try: msg = self._parser.parse(fp) finally: fp.close() # Now we need to figure out which queue of which list this # message was destined for. See verp_bounce() in # BounceRunner.py for why we do things this way. vals = [] for header in ('delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) for field in vals: to = parseaddr(field)[1] if not to: continue mo = lre.match(to) if not mo: # This isn't an address we care about continue listname, subq = mo.group('listname', 'subq') if listname in listnames: break else: # As far as we can tell, this message isn't destined for # any list on the system. What to do? syslog('error', 'Message apparently not for any list: %s', xdstname) os.rename(dstname, xdstname) continue # BAW: blech, hardcoded msgdata = {'listname': listname} # -admin is deprecated if subq in ('bounces', 'admin'): queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR) elif subq == 'confirm': msgdata['toconfirm'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('join', 'subscribe'): msgdata['tojoin'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq in ('leave', 'unsubscribe'): msgdata['toleave'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) elif subq == 'owner': msgdata.update({ 'toowner': 1, 'envsender': Utils.get_site_email(extra='bounces'), 'pipeline': mm_cfg.OWNER_PIPELINE, }) queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq is None: msgdata['tolist'] = 1 queue = get_switchboard(mm_cfg.INQUEUE_DIR) elif subq == 'request': msgdata['torequest'] = 1 queue = get_switchboard(mm_cfg.CMDQUEUE_DIR) else: syslog('error', 'Unknown sub-queue: %s', subq) os.rename(dstname, xdstname) continue queue.enqueue(msg, msgdata) os.unlink(dstname) except Exception, e: os.rename(dstname, xdstname) syslog('error', str(e))
def fixfile(inpath, input_lines, outfile): from email.Utils import parseaddr basename = os.path.basename(inpath) infile = iter(input_lines) # head header = [] pep = "" title = "" for line in infile: if not line.strip(): break if line[0].strip(): if ":" not in line: break key, value = line.split(":", 1) value = value.strip() header.append((key, value)) else: # continuation line key, value = header[-1] value = value + line header[-1] = key, value if key.lower() == "title": title = value elif key.lower() == "pep": pep = value if pep: title = "PEP " + pep + " -- " + title r = random.choice(range(64)) print >> outfile, COMMENT print >> outfile, '<div class="header">\n<table border="0" class="rfc2822">' for k, v in header: if k.lower() in ('author', 'discussions-to'): mailtos = [] for part in re.split(',\s*', v): if '@' in part: realname, addr = parseaddr(part) if k.lower() == 'discussions-to': m = linkemail(addr, pep) else: m = fixemail(addr, pep) mailtos.append('%s <%s>' % (realname, m)) elif part.startswith('http:'): mailtos.append( '<a href="%s">%s</a>' % (part, part)) else: mailtos.append(part) v = COMMASPACE.join(mailtos) elif k.lower() in ('replaces', 'replaced-by', 'requires'): otherpeps = '' for otherpep in re.split(',?\s+', v): otherpep = int(otherpep) otherpeps += PEPANCHOR % (otherpep, otherpep) v = otherpeps elif k.lower() in ('last-modified',): date = v or time.strftime('%Y-%m-%d', time.localtime(os.stat(inpath)[8])) if date.startswith('$' 'Date: ') and date.endswith(' $'): date = date[6:-2] try: url = PEPCVSURL % int(pep) v = '<a href="%s">%s</a> ' % (url, cgi.escape(date)) except ValueError, error: v = date elif k.lower() == 'content-type': url = PEPURL % 9 pep_type = v or 'text/plain' v = '<a href="%s">%s</a> ' % (url, cgi.escape(pep_type))
def process_message(self, peer, mailfrom, rcpttos, data): try: mailfrom = parseaddr(mailfrom)[1] logger.debug('Receiving message from: %s:%d' % peer) logger.debug('Message addressed from: %s' % mailfrom) logger.debug('Message addressed to: %s' % str(rcpttos)) msg = email.message_from_string(data) subject = '' for encoded_string, charset in decode_header(msg.get('Subject')): try: if charset is not None: subject += encoded_string.decode(charset) else: subject += encoded_string except: logger.exception( 'Error reading part of subject: %s charset %s' % (encoded_string, charset)) logger.debug('Subject: %s' % subject) text_parts = [] html_parts = [] attachments = {} #logger.debug('Headers: %s' % msg.items()) # YOU CAN DO SOME SECURITY CONTROLS HERE #if (not mailfrom.endswith("@hankenfeld.at") or # not msg.get('Mail-Header') == 'expected value'): # raise Exception("Email not trusted") # loop on the email parts for part in msg.walk(): if part.get_content_maintype() == 'multipart': continue c_type = part.get_content_type() c_disp = part.get('Content-Disposition') # text parts will be appended to text_parts if c_type == 'text/plain' and c_disp == None: text_parts.append(part.get_payload(decode=True).strip()) # ignore html part elif c_type == 'text/html': html_parts.append(part.get_payload(decode=True).strip()) # attachments will be sent as files in the POST request else: filename = part.get_filename() filecontent = part.get_payload(decode=True) if filecontent is not None: if filename is None: filename = 'untitled' attachments['file%d' % len(attachments)] = (filename, filecontent) body = '\n'.join(text_parts) htmlbody = '\n'.join(html_parts) except: logger.exception('Error reading incoming email') else: # this data will be sent as POST data edata = { 'subject': subject, 'body': body, 'htmlbody': htmlbody, 'from': mailfrom, 'attachments': [] } savedata = { 'sender_ip': peer[0], 'from': mailfrom, 'rcpts': rcpttos, 'raw': data, 'parsed': edata } filenamebase = str(int(round(time.time() * 1000))) for em in rcpttos: em = em.lower() if not re.match(r"[^@\s]+@[^@\s]+\.[a-zA-Z0-9]+$", em): logger.exception('Invalid recipient: %s' % em) continue domain = em.split('@')[1] if (DISCARD_UNKNOWN and not domain in DOMAINS): logger.info('Discarding email for unknown domain: %s' % domain) continue if not os.path.exists("../data/" + em): os.mkdir("../data/" + em, 0o755) #same attachments if any for att in attachments: if not os.path.exists("../data/" + em + "/attachments"): os.mkdir("../data/" + em + "/attachments", 0o755) attd = attachments[att] file = open( "../data/" + em + "/attachments/" + filenamebase + "-" + attd[0], 'wb') file.write(attd[1]) file.close() edata["attachments"].append(filenamebase + "-" + attd[0]) # save actual json data with open("../data/" + em + "/" + filenamebase + ".json", "w") as outfile: json.dump(savedata, outfile) #print edata cleanup() return
def sendNotificationEmail(self, addresses, subject, rstText): """ Send a notification email to the list of addresses XXX Note to self [maurits]: look at this blog post from Marius Gedminas, titled "Sending Unicode emails in Python": http://mg.pov.lt/blog/unicode-emails-in-python.html """ if not self.getSendNotificationEmailsTo() or not addresses: return portal_url = getToolByName(self, 'portal_url') plone_utils = getToolByName(self, 'plone_utils') portal = portal_url.getPortalObject() mailHost = plone_utils.getMailHost() charset = portal.getProperty('email_charset', '') if not charset: charset = plone_utils.getSiteEncoding() from_address = portal.getProperty('email_from_address', '') if not from_address: log('Cannot send notification email: email sender address not set') return from_name = portal.getProperty('email_from_name', '') mfrom = formataddr((from_name, from_address)) if parseaddr(mfrom)[1] != from_address: # formataddr probably got confused by special characters. mfrom = from_address email_msg = MIMEMultipart('alternative') email_msg.epilogue = '' # Translate the body text ts = getGlobalTranslationService() rstText = ts.translate('Poi', rstText, context=self) # We must choose the body charset manually for body_charset in 'US-ASCII', charset, 'UTF-8': try: rstText = rstText.encode(body_charset) except UnicodeError: pass else: break textPart = MIMEText(rstText, 'plain', body_charset) email_msg.attach(textPart) htmlPart = MIMEText(renderHTML(rstText, charset=body_charset), 'html', body_charset) email_msg.attach(htmlPart) # Make the subject unicode and translate it too. subject = safe_unicode(subject, charset) subject = ts.translate('Poi', subject, context=self) for address in addresses: address = safe_unicode(address, charset) if not address: # E-mail address may not be known, for example in case # of LDAP users. See: # http://plone.org/products/poi/issues/213 continue try: # Note that charset is only used for the headers, not # for the body text as that is a Message/MIMEText already. mailHost.secureSend(message=email_msg, mto=address, mfrom=mfrom, subject=subject, charset=charset) except (socket.error, SMTPException), exc: log_exc(('Could not send email from %s to %s regarding issue ' 'in tracker %s\ntext is:\n%s\n') % (mfrom, address, self.absolute_url(), email_msg)) log_exc("Reason: %s: %r" % (exc.__class__.__name__, str(exc))) except: