-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.Debug import log
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 <user@domain.com>, PL User <test@planet-lab.org>'
- otherwise:
- input: {'user@domain.com': 'A User','test@planet-lab.org': 'PL User'}
- ouput: ['A User <user@domain.com>', 'PL User <test@planet-lab.org>']
-
- """
- 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" <recipient@domain>
+
+ 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:
+ print >> log, "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())