X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2Fsendmail.py;h=a5f0c5e8521c76019bd711045b4d1c334a94a50e;hb=19d4a01ccf66af9e00914351b3eacd5fc880f988;hp=408f2cbaab1f960c197b33cdfcd1858dd193631d;hpb=b41a8feac3c76727d2efdeeeec09c4593f4bac3a;p=plcapi.git diff --git a/PLC/sendmail.py b/PLC/sendmail.py index 408f2cb..a5f0c5e 100644 --- a/PLC/sendmail.py +++ b/PLC/sendmail.py @@ -1,144 +1,99 @@ -import smtplib -import string +import os import sys -import time +import pprint +from types import StringTypes +from email.MIMEText import MIMEText +from email.Header import Header +from smtplib import SMTP +from PLC.Logger import logger from PLC.Faults import * -""" -used to send out emails - sets up the headers correctly -""" -class sendmail: - - def __init__(self, config): - - self.MAIL_ENABLED = config.PLC_MAIL_ENABLED - - self.SMTP_SERVER= 'localhost' - - self.X_MAILER= 'PlanetLab API Mailer' - - +def sendmail(api, To, Subject, Body, From = None, Cc = None, Bcc = None): """ - open up a connect to our mail server, and send out a message. - - the email addresses are not verified before sending out the message. - - to_addr_list, cc_addr_list, and from_addr should be a dictionary, with the keys - being the email address and the value being the plain text name of - the recipient. only the first key from from_addr will be used, so it should - contain only one address. - - subject is not checked for multiple lines - ensure it is only one. + Uses sendmail (must be installed and running locally) to send a + message to the specified recipients. If the API is running under + mod_python, the apache user must be listed in e.g., + /etc/mail/trusted-users. + + To, Cc, and Bcc may be addresses or lists of addresses. Each + address may be either a plain text address or a tuple of (name, + address). """ - def mail( self, to_addr_list, cc_addr_list, from_addr, subject, content ): - - if self.MAIL_ENABLED == 0: - return 1 - - server= smtplib.SMTP(self.SMTP_SERVER) - - str_date= time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) - - str_from_addr= self.create_recipient_list(from_addr,1) - str_to_addr= self.create_recipient_list(to_addr_list) - - full_message = "" - full_message += "Content-type: text/plain; charset=iso-8859-1\r\n" - full_message += "Date: %s\r\n" % str_date - full_message += "MIME-Version: 1.0\r\n" - - full_message += "To: %s\r\n" % str_to_addr - full_message += "From: %s\r\n" % str_from_addr - - if cc_addr_list is not None and len(cc_addr_list) > 0: - str_cc_addr= self.create_recipient_list(cc_addr_list) - full_message += "Cc: %s\r\n" % str_cc_addr - - full_message += "Subject: %s\r\n" % subject - full_message += "Reply-To: %s\r\n" % str_from_addr - full_message += "X-Mailer: %s\r\n\r\n" % self.X_MAILER - full_message += content - - try: - # get just a list of to recipients (needed for envelope). this - # needs to include all reciepients, including Cc - local_to_list= to_addr_list.copy() - - if cc_addr_list is not None: - local_to_list.update( cc_addr_list ) - - all_to_addr_list= self.create_recipient_list(local_to_list, - return_string=0) - - # send out the mail! - rc= server.sendmail(str_from_addr, all_to_addr_list, full_message ) - - return 1 - - except smtplib.SMTPRecipientsRefused, err: - sys.stderr.write( "SMTPRecipientsRefused: %s" % repr(err) ) - return 0 - - except smtplib.SMTPHeloError: - sys.stderr.write( "SMTPHeloError: %s" % repr(err) ) - return 0 - - except smtplib.SMTPSenderRefused: - sys.stderr.write( "SMTPSenderRefused: %s" % repr(err) ) - return 0 - - except smtplib.SMTPDataError: - sys.stderr.write( "SMTPDataError: %s" % repr(err) ) - return 0 - - - - - """ - accepts a list of email recipient as a dictionary in the same format - as the mail function, and returns it in a format suitable for use in - an email message. for example: - - if limit is specified, only limit number of entries from - addr_list is used. which one is used is not defined, so it is really only - useful to make sure that the result has a single entry (for from lines) - - for return_string= 1: - input: {'user@domain.com': 'A User','test@planet-lab.org': 'PL User'} - ouput: 'A User , PL User ' - otherwise: - input: {'user@domain.com': 'A User','test@planet-lab.org': 'PL User'} - ouput: ['A User ', 'PL User '] - - """ - def create_recipient_list( self, addr_list, return_string= 1, limit= None ): - if not isinstance(addr_list,dict): - raise PLCAPIError, \ - "Internal error, call to create_recipient_list " \ - "with non-dict addr_list (%s)." % str(addr_list) - - if limit == None: - limit= len(addr_list.keys()) - - if limit <= 0: - return '' - - recipients= [] - total= 0 - - for email in addr_list.keys(): - recipients = recipients + ['%s <%s>' % (addr_list[email],email)] - total= total+1 - if total == limit: - break - - if return_string == 1: - return string.join( recipients, ", " ) - else: - return recipients - - - - + # Fix up defaults + if not isinstance(To, list): + To = [To] + if Cc is not None and not isinstance(Cc, list): + Cc = [Cc] + if Bcc is not None and not isinstance(Bcc, list): + Bcc = [Bcc] + if From is None: + From = ("%s Support" % api.config.PLC_NAME, + api.config.PLC_MAIL_SUPPORT_ADDRESS) + + # Create a MIME-encoded UTF-8 message + msg = MIMEText(Body.encode(api.encoding), _charset = api.encoding) + + # Unicode subject headers are automatically encoded correctly + msg['Subject'] = Subject + + def encode_addresses(addresses, header_name = None): + """ + Unicode address headers are automatically encoded by + email.Header, but not correctly. The correct way is to put the + textual name inside quotes and the address inside brackets: + + To: "=?utf-8?b?encoded" + + Each address in addrs may be a tuple of (name, address) or + just an address. Returns a tuple of (header, addrlist) + representing the encoded header text and the list of plain + text addresses. + """ + + header = [] + addrs = [] + + for addr in addresses: + if isinstance(addr, tuple): + (name, addr) = addr + try: + name = name.encode('ascii') + header.append('%s <%s>' % (name, addr)) + except: + h = Header(name, charset = api.encoding, header_name = header_name) + header.append('"%s" <%s>' % (h.encode(), addr)) + else: + header.append(addr) + addrs.append(addr) + + return (", ".join(header), addrs) + + (msg['From'], from_addrs) = encode_addresses([From], 'From') + (msg['To'], to_addrs) = encode_addresses(To, 'To') + + if Cc is not None: + (msg['Cc'], cc_addrs) = encode_addresses(Cc, 'Cc') + to_addrs += cc_addrs + + if Bcc is not None: + (unused, bcc_addrs) = encode_addresses(Bcc, 'Bcc') + to_addrs += bcc_addrs + + # Needed to pass some spam filters + msg['Reply-To'] = msg['From'] + msg['X-Mailer'] = "Python/" + sys.version.split(" ")[0] + + if not api.config.PLC_MAIL_ENABLED: + logger.info("PLC_MAIL_ENABLED not set") + logger.info("From: %(From)s, To: %(To)s, Subject: %(Subject)s" % msg) + return + + s = SMTP() + s.connect() + rejected = s.sendmail(from_addrs[0], to_addrs, msg.as_string(), rcpt_options = ["NOTIFY=NEVER"]) + s.close() + + if rejected: + raise PLCAPIError, "Error sending message to " + ", ".join(rejected.keys())