Skip to content

Commit

Permalink
Branch for removing imports from the Globals module.
Browse files Browse the repository at this point in the history
  • Loading branch information
tseaver committed Dec 27, 2008
0 parents commit 98329c1
Showing 1 changed file with 394 additions and 0 deletions.
394 changes: 394 additions & 0 deletions MailHost.py
@@ -0,0 +1,394 @@
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""SMTP mail objects
$Id$
"""
from cStringIO import StringIO
import logging
import mimetools
import rfc822
from threading import Lock
import time

from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.Permissions import change_configuration, view
from AccessControl.Permissions import use_mailhost_services
from AccessControl.Role import RoleManager
from Acquisition import Implicit
from App.class_init import default__class_init__ as InitializeClass
from App.special_dtml import DTMLFile
from DateTime.DateTime import DateTime
from Persistence import Persistent
from OFS.SimpleItem import Item

from zope.interface import implements
from zope.sendmail.maildir import Maildir
from zope.sendmail.delivery import DirectMailDelivery, QueuedMailDelivery, \
QueueProcessorThread

from interfaces import IMailHost
from decorator import synchronized

# Use our own TLS/SSL-aware mailer since the zope.sendmail does
# not support TLS/SSL in this version (should be replaced with
# the next version)
from mailer import SMTPMailer

queue_threads = {} # maps MailHost path -> queue processor threada

LOG = logging.getLogger('MailHost')

class MailHostError(Exception):
pass

manage_addMailHostForm = DTMLFile('dtml/addMailHost_form', globals())
def manage_addMailHost(self,
id,
title='',
smtp_host='localhost',
localhost='localhost',
smtp_port=25,
timeout=1.0,
REQUEST=None,
):
""" Add a MailHost into the system.
"""
i = MailHost( id, title, smtp_host, smtp_port ) #create new mail host
self._setObject( id,i ) #register it

if REQUEST is not None:
REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

add = manage_addMailHost


class MailBase(Implicit, Item, RoleManager):
"""a mailhost...?"""

implements(IMailHost)

meta_type = 'Mail Host'
manage = manage_main = DTMLFile('dtml/manageMailHost', globals())
manage_main._setName('manage_main')
index_html = None
security = ClassSecurityInfo()
smtp_uid = '' # Class attributes for smooth upgrades
smtp_pwd = ''
smtp_queue = False
smtp_queue_directory = '/tmp'
force_tls = False
lock = Lock()

# timeout = 1.0 # unused?


manage_options = (
(
{'icon':'', 'label':'Edit',
'action':'manage_main',
'help':('MailHost','Mail-Host_Edit.stx')},
)
+ RoleManager.manage_options
+ Item.manage_options
)


def __init__(self,
id='',
title='',
smtp_host='localhost',
smtp_port=25,
force_tls=False,
smtp_uid='',
smtp_pwd='',
smtp_queue=False,
smtp_queue_directory='/tmp',
):
"""Initialize a new MailHost instance.
"""
self.id = id
self.title = title
self.smtp_host = str( smtp_host )
self.smtp_port = int(smtp_port)
self.smtp_uid = smtp_uid
self.smtp_pwd = smtp_pwd
self.force_tls = force_tls
self.smtp_queue = smtp_queue
self.smtp_queue_directory = smtp_queue_directory


# staying for now... (backwards compatibility)
def _init(self, smtp_host, smtp_port):
self.smtp_host = smtp_host
self.smtp_port = smtp_port

security.declareProtected(change_configuration, 'manage_makeChanges')
def manage_makeChanges(self,
title,
smtp_host,
smtp_port,
smtp_uid='',
smtp_pwd='',
smtp_queue=False,
smtp_queue_directory='/tmp',
force_tls=False,
REQUEST=None,
):
"""Make the changes.
"""
title = str(title)
smtp_host = str(smtp_host)
smtp_port = int(smtp_port)

self.title = title
self.smtp_host = smtp_host
self.smtp_port = smtp_port
self.smtp_uid = smtp_uid
self.smtp_pwd = smtp_pwd
self.force_tls = force_tls
self.smtp_queue = smtp_queue
self.smtp_queue_directory = smtp_queue_directory

# restart queue processor thread
if self.smtp_queue:
self._stopQueueProcessorThread()
self._startQueueProcessorThread()
else:
self._stopQueueProcessorThread()


if REQUEST is not None:
msg = 'MailHost %s updated' % self.id
return self.manage_main( self
, REQUEST
, manage_tabs_message=msg
)

security.declareProtected(use_mailhost_services, 'sendTemplate')
def sendTemplate(trueself,
self,
messageTemplate,
statusTemplate=None,
mto=None,
mfrom=None,
encode=None,
REQUEST=None,
immediate=False,
):
"""Render a mail template, then send it...
"""
mtemplate = getattr(self, messageTemplate)
messageText = mtemplate(self, trueself.REQUEST)
messageText, mto, mfrom = _mungeHeaders( messageText, mto, mfrom)
messageText = _encode(messageText, encode)
trueself._send(mfrom, mto, messageText, immediate)

if not statusTemplate:
return "SEND OK"

try:
stemplate = getattr(self, statusTemplate)
return stemplate(self, trueself.REQUEST)
except:
return "SEND OK"

security.declareProtected(use_mailhost_services, 'send')
def send(self,
messageText,
mto=None,
mfrom=None,
subject=None,
encode=None,
immediate=False,
):

messageText, mto, mfrom = _mungeHeaders(messageText,
mto, mfrom, subject)
messageText = _encode(messageText, encode)
self._send(mfrom, mto, messageText, immediate)

# This is here for backwards compatibility only. Possibly it could
# be used to send messages at a scheduled future time, or via a mail queue?
security.declareProtected(use_mailhost_services, 'scheduledSend')
scheduledSend = send

security.declareProtected(use_mailhost_services, 'simple_send')
def simple_send(self, mto, mfrom, subject, body, immediate=False):
body = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (
mfrom, mto, subject, body)

self._send(mfrom, mto, body, immediate)


def _makeMailer(self):
""" Create a SMTPMailer """
return SMTPMailer(hostname=self.smtp_host,
port=int(self.smtp_port),
username=self.smtp_uid or None,
password=self.smtp_pwd or None,
force_tls=self.force_tls
)

@synchronized(lock)
def _stopQueueProcessorThread(self):
""" Stop thread for processing the mail queue """

path = self.absolute_url(1)
if queue_threads.has_key(path):
thread = queue_threads[path]
thread.stop()
while thread.isAlive():
# wait until thread is really dead
time.sleep(0.3)
del queue_threads[path]
LOG.info('Thread for %s stopped' % path)

@synchronized(lock)
def _startQueueProcessorThread(self):
""" Start thread for processing the mail queue """

path = self.absolute_url(1)
if not queue_threads.has_key(path):
thread = QueueProcessorThread()
thread.setMailer(self._makeMailer())
thread.setQueuePath(self.smtp_queue_directory)
thread.start()
queue_threads[path] = thread
LOG.info('Thread for %s started' % path)

security.declareProtected(view, 'queueLength')
def queueLength(self):
""" return length of mail queue """

try:
maildir = Maildir(self.smtp_queue_directory)
return len([item for item in maildir])
except ValueError:
return 'n/a - %s is not a maildir - please verify your ' \
'configuration' % self.smtp_queue_directory


security.declareProtected(view, 'queueThreadAlive')
def queueThreadAlive(self):
""" return True/False is queue thread is working """

th = queue_threads.get(self.absolute_url(1))
if th:
return th.isAlive()
return False

security.declareProtected(change_configuration, 'manage_restartQueueThread')
def manage_restartQueueThread(self, action='start', REQUEST=None):
""" Restart the queue processor thread """

if action == 'stop':
self._stopQueueProcessorThread()
elif action == 'start':
self._startQueueProcessorThread()
else:
raise ValueError('Unsupported action %s' % action)

if REQUEST is not None:
msg = 'Queue processor thread %s' % \
(action == 'stop' and 'stopped' or 'started')
return self.manage_main(self, REQUEST, manage_tabs_message=msg)


security.declarePrivate('_send')
def _send(self, mfrom, mto, messageText, immediate=False):
""" Send the message """

if immediate:
self._makeMailer().send(mfrom, mto, messageText)
else:
if self.smtp_queue:
# Start queue processor thread, if necessary
self._startQueueProcessorThread()
delivery = QueuedMailDelivery(self.smtp_queue_directory)
else:
delivery = DirectMailDelivery(self._makeMailer())

delivery.send(mfrom, mto, messageText)

InitializeClass(MailBase)


class MailHost(Persistent, MailBase):
"""persistent version"""


def _encode(body, encode=None):
if encode is None:
return body
mfile = StringIO(body)
mo = mimetools.Message(mfile)
if mo.getencoding() != '7bit':
raise MailHostError, 'Message already encoded'
newmfile = StringIO()
newmfile.write(''.join(mo.headers))
newmfile.write('Content-Transfer-Encoding: %s\n' % encode)
if not mo.has_key('Mime-Version'):
newmfile.write('Mime-Version: 1.0\n')
newmfile.write('\n')
mimetools.encode(mfile, newmfile, encode)
return newmfile.getvalue()

def _mungeHeaders( messageText, mto=None, mfrom=None, subject=None):
"""Sets missing message headers, and deletes Bcc.
returns fixed message, fixed mto and fixed mfrom"""
mfile = StringIO(messageText.lstrip())
mo = rfc822.Message(mfile)

# Parameters given will *always* override headers in the messageText.
# This is so that you can't override or add to subscribers by adding
# them to # the message text.
if subject:
mo['Subject'] = subject
elif not mo.getheader('Subject'):
mo['Subject'] = '[No Subject]'

if mto:
if isinstance(mto, basestring):
mto = [rfc822.dump_address_pair(addr)
for addr in rfc822.AddressList(mto) ]
if not mo.getheader('To'):
mo['To'] = ','.join(mto)
else:
mto = []
for header in ('To', 'Cc', 'Bcc'):
v = mo.getheader(header)
if v:
mto += [rfc822.dump_address_pair(addr)
for addr in rfc822.AddressList(v)]
if not mto:
raise MailHostError, "No message recipients designated"

if mfrom:
mo['From'] = mfrom
else:
if mo.getheader('From') is None:
raise MailHostError,"Message missing SMTP Header 'From'"
mfrom = mo['From']

if mo.getheader('Bcc'):
mo.__delitem__('Bcc')

if not mo.getheader('Date'):
mo['Date'] = DateTime().rfc822()

mo.rewindbody()
finalmessage = mo
finalmessage = mo.__str__() + '\n' + mfile.read()
mfile.close()
return finalmessage, mto, mfrom

0 comments on commit 98329c1

Please sign in to comment.