def getPlainBodyFromMail(self, mailString): # get content-type and body from mail given as string return MailBoxerTools.getPlainBodyFromMail(mailString)
def unpackMail(self, mailString): # returns plainbody, content-type, htmlbody and attachments return MailBoxerTools.unpackMail(mailString)
def lowerList(self, stringlist): # lowers all items of a list return MailBoxerTools.lowerList(stringlist)
def HtmlToText(self, html): # converts html to text return MailBoxerTools.convertHTML2Text(html)
def parseaddr(self, header): # wrapper for rfc822.parseaddr, returns (name, addr) return MailBoxerTools.parseaddr(header)
def parseaddrList(self, header): # wrapper for rfc822.AddressList, returns list of (name, addr) return MailBoxerTools.parseaddrList(header)
def splitMail(self, mailString): # return (header,body) of a mail given as string return MailBoxerTools.splitMail(mailString)
def mime_decode_header(self, header): # Returns the unfolded and undecoded header return MailBoxerTools.mime_decode_header(header)
def send(callURL, mailString, maxBytes=None): # If you wish to use HTTP Basic Authentication, set a user id and password here. # Alternatively you can call the URL like: # http://username:password@yourHost/MailBoxer/manage_mailboxer # Note that this is not necessary in the default MailBoxer configuration, but # may be used to add some extra security. # Format: username:password AUTHORIZATION='' # If you want to strip out all attachments, leaving only plain text, set this. # If you have a email size limit set, it will apply on what is left _after_ # attachment stripping. This will also trigger an 'error' message to be # (optionally) generated by MailBoxer, which provides an opportunity to # notify the sender of the attachment being stripped. # # If attachments are to be stripped, MailBoxerTools.py must also be available # in your PYTHONPATH. If MailBoxerTools.py is not available, this will be set # back to 0. STRIP_ATTACHMENTS = 0 # Notify Zope-side MailBoxer of events (such as attachments that have been # stripped). Messages will still be logged to the syslog (if available). EVENT_NOTIFICATION = 0 # If you have a special setup which don't allow locking / serialization, # set USE_LOCKS = 0 USE_LOCKS = 1 # This should work with Unix & Windows & MacOS, # if not, set it on your own (e.g. '/tmp/smtp2zope.lock'). LOCKFILE_LOCATION = os.path.join(os.path.split(tempfile.mktemp())[0], 'smtp2zope.lock') # The amount of time to wait to be serialised LOCK_TIMEOUT = 15 #seconds # Meaningful exit-codes for a smtp-server EXIT_USAGE = 64 EXIT_NOUSER = 67 EXIT_NOPERM = 77 EXIT_TEMPFAIL = 75 if callURL.find('http://') == -1: raise Exception('URL is specified (%s) is not a valid URL' % callURL) urlParts = urllib2.urlparse.urlparse(callURL) urlPath = '/'.join(filter(None, list(urlParts)[2].split('/'))[:-1]) baseURL = urllib2.urlparse.urlunparse(urlParts[:2]+(urlPath,)+urlParts[3:])+'/' # Check for authentication-string (username:passwd) in URL # Url looks like: http://username:passwd@host/... auth_mark = callURL.find('@') if auth_mark<>-1: AUTHORIZATION = callURL[7:auth_mark] callURL = callURL.replace(AUTHORIZATION+'@','') # Check for optional maxBytes if maxBytes is not None: try: maxBytes = long(maxBytes) except ValueError: raise Exception('the specified value of maxBytes (%s) was not an integer' % maxBytes) else: maxBytes = 0 # means: unlimited!!! if USE_LOCKS: # Create temporary lockfile lock = LockFile(LOCKFILE_LOCATION) try: lock.lock(LOCK_TIMEOUT) except TimeOutError: raise Exception('Serialisation timeout occurred, will request message to be requeued') event_codes = [] if STRIP_ATTACHMENTS: # check to see if we have attachments text_body, content_type, html_body, attachments = MailBoxerTools.unpackMail(mailString) num_attachments = len(attachments) if num_attachments or html_body: content_type, text_body = MailBoxerTools.getPlainBodyFromMail(mailString) headers = MailBoxerTools.headersAsString(mailString, {'Content-Type': content_type}) mailString = '%s\r\n\r\n%s' % (headers, text_body) if html_body: event_codes.append(100) # stripped HTML if num_attachments > 1: # we had a HTML _and_ attachments event_codes.append(101) # stripped attachments elif num_attachments: event_codes.append(101) # stripped attachments # Check its size mailLen = len(mailString) if maxBytes>0 and mailLen>maxBytes: log_warning('Rejecting email, due to size (%s bytes, limit %s bytes)' % (mailLen, maxBytes)) event_codes.append(200) # email too long if not EVENT_NOTIFICATION: sys.exit(EXIT_NOPERM) else: eventNotification(baseURL, event_codes, mailString) sys.exit(0) # Transfer mail to http-server. # urllib2 handles server-responses (errors) much better than urllib. # urllib2 is, in fact, too much better. Its built-in redirect handler # can mask authorization problems when a cookie-based authenticator is in # use -- as with Plone. Also, I can't think of any reason why we would # want to allow redirection of these requests! # # So, let's create and install a disfunctional redirect handler. # Install the broken redirect handler opener = urllib2.build_opener(BrokenHTTPRedirectHandler()) urllib2.install_opener(opener) try: req = urllib2.Request(callURL) if AUTHORIZATION: auth = base64.encodestring(AUTHORIZATION).strip() req.add_header('Authorization', 'Basic %s' % auth) response = urllib2.urlopen(req, data='Mail='+urllib.quote(mailString)) except Exception, e: # If MailBoxer doesn't exist, bounce message with EXIT_NOUSER, # so the sender will receive a "user-doesn't-exist"-mail from MTA. if hasattr(e, 'code'): if e.code == 404: raise Exception("URL at %s doesn't exist (%s)" % (callURL, e)) else: # Server down? EXIT_TEMPFAIL causes the MTA to try again later. raise Exception('A problem, "%s", occurred uploading email to URL %s (error code was %s)' % (e, callURL, e.code)) else: # Server down? EXIT_TEMPFAIL causes the MTA to try again later. raise Exception('A problem, "%s", occurred uploading email to server %s' % (e, callURL))
def eventNotification(url, event_codes, mailString): event_codes = tuple(event_codes) if EVENT_NOTIFICATION and event_codes: server = xmlrpclib.ServerProxy(url) headers, body = MailBoxerTools.splitMail(mailString) server.manage_event(event_codes, headers)
if USE_LOCKS: # Create temporary lockfile lock = LockFile(LOCKFILE_LOCATION) try: lock.lock(LOCK_TIMEOUT) except TimeOutError: log_info('Serialisation timeout occurred, will request message to be requeued') sys.exit(EXIT_TEMPFAIL) # Get the raw mail mailString = sys.stdin.read() event_codes = [] if STRIP_ATTACHMENTS: # check to see if we have attachments text_body, content_type, html_body, attachments = MailBoxerTools.unpackMail(mailString) num_attachments = len(attachments) if num_attachments or html_body: content_type, text_body = MailBoxerTools.getPlainBodyFromMail(mailString) headers = MailBoxerTools.headersAsString(mailString, {'Content-Type': content_type}) mailString = '%s\r\n\r\n%s' % (headers, text_body) if html_body: event_codes.append(100) # stripped HTML if num_attachments > 1: # we had a HTML _and_ attachments event_codes.append(101) # stripped attachments elif num_attachments: event_codes.append(101) # stripped attachments # Check its size
def send(callURL, mailString, maxBytes=None): # If you wish to use HTTP Basic Authentication, set a user id and password here. # Alternatively you can call the URL like: # http://username:password@yourHost/MailBoxer/manage_mailboxer # Note that this is not necessary in the default MailBoxer configuration, but # may be used to add some extra security. # Format: username:password AUTHORIZATION = '' # If you want to strip out all attachments, leaving only plain text, set this. # If you have a email size limit set, it will apply on what is left _after_ # attachment stripping. This will also trigger an 'error' message to be # (optionally) generated by MailBoxer, which provides an opportunity to # notify the sender of the attachment being stripped. # # If attachments are to be stripped, MailBoxerTools.py must also be available # in your PYTHONPATH. If MailBoxerTools.py is not available, this will be set # back to 0. STRIP_ATTACHMENTS = 0 # Notify Zope-side MailBoxer of events (such as attachments that have been # stripped). Messages will still be logged to the syslog (if available). EVENT_NOTIFICATION = 0 # If you have a special setup which don't allow locking / serialization, # set USE_LOCKS = 0 USE_LOCKS = 1 # This should work with Unix & Windows & MacOS, # if not, set it on your own (e.g. '/tmp/smtp2zope.lock'). LOCKFILE_LOCATION = os.path.join( os.path.split(tempfile.mktemp())[0], 'smtp2zope.lock') # The amount of time to wait to be serialised LOCK_TIMEOUT = 15 #seconds # Meaningful exit-codes for a smtp-server EXIT_USAGE = 64 EXIT_NOUSER = 67 EXIT_NOPERM = 77 EXIT_TEMPFAIL = 75 if callURL.find('http://') == -1: raise Exception('URL is specified (%s) is not a valid URL' % callURL) urlParts = urllib2.urlparse.urlparse(callURL) urlPath = '/'.join(filter(None, list(urlParts)[2].split('/'))[:-1]) baseURL = urllib2.urlparse.urlunparse(urlParts[:2] + (urlPath, ) + urlParts[3:]) + '/' # Check for authentication-string (username:passwd) in URL # Url looks like: http://username:passwd@host/... auth_mark = callURL.find('@') if auth_mark <> -1: AUTHORIZATION = callURL[7:auth_mark] callURL = callURL.replace(AUTHORIZATION + '@', '') # Check for optional maxBytes if maxBytes is not None: try: maxBytes = long(maxBytes) except ValueError: raise Exception( 'the specified value of maxBytes (%s) was not an integer' % maxBytes) else: maxBytes = 0 # means: unlimited!!! if USE_LOCKS: # Create temporary lockfile lock = LockFile(LOCKFILE_LOCATION) try: lock.lock(LOCK_TIMEOUT) except TimeOutError: raise Exception( 'Serialisation timeout occurred, will request message to be requeued' ) event_codes = [] if STRIP_ATTACHMENTS: # check to see if we have attachments text_body, content_type, html_body, attachments = MailBoxerTools.unpackMail( mailString) num_attachments = len(attachments) if num_attachments or html_body: content_type, text_body = MailBoxerTools.getPlainBodyFromMail( mailString) headers = MailBoxerTools.headersAsString( mailString, {'Content-Type': content_type}) mailString = '%s\r\n\r\n%s' % (headers, text_body) if html_body: event_codes.append(100) # stripped HTML if num_attachments > 1: # we had a HTML _and_ attachments event_codes.append(101) # stripped attachments elif num_attachments: event_codes.append(101) # stripped attachments # Check its size mailLen = len(mailString) if maxBytes > 0 and mailLen > maxBytes: log_warning('Rejecting email, due to size (%s bytes, limit %s bytes)' % (mailLen, maxBytes)) event_codes.append(200) # email too long if not EVENT_NOTIFICATION: sys.exit(EXIT_NOPERM) else: eventNotification(baseURL, event_codes, mailString) sys.exit(0) # Transfer mail to http-server. # urllib2 handles server-responses (errors) much better than urllib. # urllib2 is, in fact, too much better. Its built-in redirect handler # can mask authorization problems when a cookie-based authenticator is in # use -- as with Plone. Also, I can't think of any reason why we would # want to allow redirection of these requests! # # So, let's create and install a disfunctional redirect handler. # Install the broken redirect handler opener = urllib2.build_opener(BrokenHTTPRedirectHandler()) urllib2.install_opener(opener) try: req = urllib2.Request(callURL) if AUTHORIZATION: auth = base64.encodestring(AUTHORIZATION).strip() req.add_header('Authorization', 'Basic %s' % auth) response = urllib2.urlopen(req, data='Mail=' + urllib.quote(mailString)) except Exception, e: # If MailBoxer doesn't exist, bounce message with EXIT_NOUSER, # so the sender will receive a "user-doesn't-exist"-mail from MTA. if hasattr(e, 'code'): if e.code == 404: raise Exception("URL at %s doesn't exist (%s)" % (callURL, e)) else: # Server down? EXIT_TEMPFAIL causes the MTA to try again later. raise Exception( 'A problem, "%s", occurred uploading email to URL %s (error code was %s)' % (e, callURL, e.code)) else: # Server down? EXIT_TEMPFAIL causes the MTA to try again later. raise Exception( 'A problem, "%s", occurred uploading email to server %s' % (e, callURL))