Changeset 52 for branches

Show
Ignore:
Timestamp:
10/26/07 05:48:32 (1 year ago)
Author:
amcgregor
Message:

TurboMail 3.0 is now usable: the smtp and debug providers are working, immediate and demand managers are good-to-go, and S/MIME signatures work, too\!

Location:
branches/3.0
Files:
11 modified

Legend:

Unmodified
Added
Removed
  • branches/3.0/setup.py

    r44 r52  
    2828                ], 
    2929                extras_require = { 
    30                         'turbogears': ["TurboGears>=1.0"] 
     30                        'turbogears': ["TurboGears>=1.0"], 
     31                        'smime': ["M2Crypto>=0.18"] 
    3132                }, 
    3233                zip_safe=True, 
     
    5051                        ], 
    5152                        'turbomail.managers': [ 
    52 #                               "demand = turbomail.managers.demand", 
     53                                "demand = turbomail.managers.demand", 
    5354#                               "polling = turbomail.managers.polling", 
    5455                                "immediate = turbomail.managers.immediate" 
    5556                        ], 
    5657                        'turbomail.providers': [ 
    57 #                               "smtp = turbomail.providers.smtp", 
     58                                "smtp = turbomail.providers.smtp", 
    5859#                               "sendmail = turbomail.providers.sendmail" 
    5960#                               "disk = turbomail.providers.disk", 
     
    6263                        'turbomail.extensions': [ 
    6364                                "utf8qp = turbomail.extensions.utf8qp", 
    64 #                               "smime = turbomail.extensions.smime", 
     65                                "smime = turbomail.extensions.smime", 
    6566#                               "gpg = turbomail.extensions.gpg", 
    6667                        ] 
  • branches/3.0/tests/test_message.py

    r36 r52  
    3030         
    3131        def test_message_string(self): 
     32                self.failUnless("To: Recipient <recipient@example.com>" in str(self.message)) 
     33                self.failUnless("From: Sender <sender@example.com>" in str(self.message)) 
    3234                self.failUnless("Subject: Test message subject." in str(self.message)) 
    3335                self.failUnless("\n\nThis is a test message plain text body." in str(self.message)) 
     36         
     37        def test_recipients_collection(self): 
     38                self.message.cc.append("copied@example.com") 
     39                self.assertEqual(self.message.recipients.addresses, ["recipient@example.com", "copied@example.com"]) 
  • branches/3.0/turbomail/__init__.py

    r44 r52  
    2020from turbomail.util import send 
    2121 
     22import pkg_resources 
     23 
    2224__all__ = ['exceptions', 'extensions', 'managers', 'providers', 'release', 'manager', 'provider', 'config', 'Message', 'interface', 'send'] 
    2325 
     
    2628provider = None 
    2729config = {} 
     30 
  • branches/3.0/turbomail/control.py

    r40 r52  
    6767                # Load the requested pool manager. 
    6868                manager = turbomail.config.get("mail.manager", "immediate") 
    69                 turbomail.manager = self.load_single_entry("turbomail.managers", manager).load() 
    70                 if not turbomail.manager: 
    71                         turbomail.config.set("mail.on", False) 
     69                controller = self.load_single_entry("turbomail.managers", manager) 
     70                if not controller: 
     71                        turbomail.config.update({"mail.on": False}) 
    7272                        log.error("Unable to locate %s manager, TurboMail disabled." % manager) 
     73                        self.stop(force=True) 
    7374                        return 
     75                turbomail.manager = controller.load() 
    7476                turbomail.manager.start() 
    7577                 
    7678                # Load the requested mail provider. 
    7779                provider = turbomail.config.get("mail.provider", "debug") 
    78                 turbomail.provider = self.load_single_entry("turbomail.providers", provider).load() 
    79                 if not turbomail.provider: 
    80                         turbomail.config.set("mail.on", False) 
     80                controller = self.load_single_entry("turbomail.providers", provider) 
     81                if not controller: 
     82                        turbomail.config.update({"mail.on": False}) 
    8183                        log.error("Unable to locate %s provider, TurboMail disabled." % provider) 
     84                        self.stop(force=True) 
    8285                        return 
     86                turbomail.provider = controller.load() 
    8387                turbomail.provider.start() 
    8488                 
     
    9498                self.running = True 
    9599         
    96         def stop(self): 
    97                 if not self.running: return 
     100        def stop(self, force=False): 
     101                if not self.running and not force: return 
    98102                 
    99103                log.info("TurboMail extension shutting down.") 
     
    104108                        if turbomail.config.get("mail." + entrypoint.name + ".on", False): 
    105109                                ext = entrypoint.load() 
    106                                 if hasattr(ext, "unload"): ext.unload() 
     110                                if hasattr(ext, "stop"): ext.stop() 
    107111                 
    108112                # Unload the provider and manager. 
    109                 turbomail.provider.stop() 
    110                 turbomail.manager.stop() 
     113                if turbomail.provider and hasattr(turbomail.provider, "stop"): turbomail.provider.stop() 
     114                if turbomail.manager and hasattr(turbomail.manager, "stop"): turbomail.manager.stop() 
     115                 
     116                turbomail.provider = None 
     117                turbomail.manager = None 
    111118                 
    112119                self.running = False 
  • branches/3.0/turbomail/extensions/smime.py

    r42 r52  
     1# encoding: utf-8 
     2 
     3"""TurboMail extension API.""" 
     4 
     5import logging 
     6log = logging.getLogger("turbomail.utf8qp") 
     7 
     8import turbomail, os 
     9from email.mime.multipart import MIMEMultipart 
     10from email.mime.application import MIMEApplication 
     11 
     12__all__ = ['start'] 
     13 
     14 
     15 
     16class Message(turbomail.Message): 
     17        def __init__(self, **kw): 
     18                super(Message, self).__init__(**kw) 
     19                 
     20                self.sign = kw.get("sign", turbomail.config.get("mail.message.sign", False)) 
     21                self.encrypt = kw.get("encrypt", turbomail.config.get("mail.message.encrypt", False)) 
     22                self.force = kw.get("force", turbomail.config.get("mail.smime.force", False)) 
     23         
     24        def process_mime_message(self, message): 
     25                newmessage = message 
     26                 
     27                if self.sign: 
     28                        from M2Crypto import BIO, Rand, SMIME 
     29                         
     30                        # Make a MemoryBuffer of the message. 
     31                        buf = BIO.MemoryBuffer(str(message)) 
     32                         
     33                        # Seed the PRNG. 
     34                        Rand.load_file('randpool.dat', -1) 
     35                         
     36                        # Instantiate an SMIME object; set it up; sign the buffer. 
     37                        s = SMIME.SMIME() 
     38                        s.load_key( 
     39                                        os.path.join(turbomail.config.get("mail.smime.keystore", "./"), str(self.author.addresses[0]) + '.key'), 
     40                                        os.path.join(turbomail.config.get("mail.smime.keystore", "./"), str(self.author.addresses[0]) + '.x509'), 
     41                                ) 
     42                        p7 = s.sign(buf) 
     43                         
     44                        # I'm assuming -a-lot- here.  There's got to be a better way to do this, but the M2Crypto lib sucks ass from this PoV. 
     45                        newmessage = MIMEMultipart("signed", protocol='application/x-pkcs7-signature', micalg="sha1") 
     46                        newmessage.attach(message) 
     47                         
     48                        buf = BIO.MemoryBuffer() 
     49                        p7.write_der(buf) 
     50                        signature = MIMEApplication(buf.read(), "x-pkcs7-signature") 
     51                        signature.set_param("name", "smime.p7s") 
     52                        del signature['Content-Disposition'] 
     53                        signature.add_header("Content-Disposition", "attachment", filename="smime.p7s") 
     54                         
     55                        newmessage.attach(signature) 
     56                         
     57                        # Save the PRNG's state. 
     58                        Rand.save_file('randpool.dat') 
     59                 
     60                return newmessage 
  • branches/3.0/turbomail/managers/demand.py

    r40 r52  
     1# encoding: utf-8 
     2 
     3"""TurboMail extension API.""" 
     4 
     5import logging 
     6log = logging.getLogger("turbomail.manager") 
     7 
     8import turbomail 
     9from turbomail.api import Manager 
     10from turbomail.exceptions import ProviderExhaustedException 
     11 
     12import math, copy 
     13from Queue import Queue, Empty 
     14from threading import Event, Thread 
     15from turbomail.dispatch import Dispatch 
     16 
     17__all__ = ['load'] 
     18 
     19 
     20def load(): 
     21        return DemandManager() 
     22 
     23 
     24class DemandManager(Manager): 
     25        name = "Demand" 
     26        version = "1.0" 
     27        url = "http://www.python-turbomail.org/wiki/DemandManager" 
     28         
     29        def __init__(self): 
     30                log.info("Demand manager starting up.") 
     31                 
     32                super(DemandManager, self).__init__() 
     33                 
     34                self.pool = 0 
     35                self.queue = Queue() 
     36                self.finished = Event() 
     37                 
     38                self.threads = turbomail.config.get("mail.demand.threads", 4) # Maximum number of threads to create. 
     39                self.divisor = turbomail.config.get("mail.demand.divisor", 10) # Estimate the number of required threads by dividing the queue size by this. 
     40                self.timeout = turbomail.config.get("mail.demand.timeout", 60) 
     41                 
     42                log.info("Demand manager ready.") 
     43         
     44        def optimum(self): 
     45                return min(self.threads, math.ceil(self.queue.qsize() / float(self.divisor))) 
     46         
     47        optimum = property(optimum) 
     48         
     49        def stop(self): 
     50                log.info("Demand manager shutting down.") 
     51                self.finished.set() 
     52         
     53        def spawn(self): 
     54                thread = Thread(target=self.wrapper) 
     55                thread.start() 
     56                self.pool += 1 
     57                 
     58        def deliver(self, message): 
     59                log.info("Adding message %s to the queue for background delivery." % message.id) 
     60                self.queue.put(copy.deepcopy(message)) 
     61                message._processed = True 
     62                message._dirty = True 
     63                 
     64                if not self.queue.empty() and self.pool < self.optimum: 
     65                        tospawn = int(self.optimum - self.pool) 
     66                        log.debug("Spawning %d thread%s." % (tospawn, tospawn != 1 and "s" or "")) 
     67                        for i in range(tospawn): 
     68                                self.spawn() 
     69                 
     70        def wrapper(self): 
     71                log.debug("Mail queue worker starting up.") 
     72                 
     73                self.worker() 
     74                 
     75                self.pool -= 1 
     76                log.debug("Mail queue worker finished.") 
     77         
     78        def worker(self): 
     79                log.debug("Requesting new provider instance.") 
     80                provider = turbomail.provider.new() 
     81                if not provider: raise ManagerException, "Unable to allocate new provider." 
     82                 
     83                while True: 
     84                        try: 
     85                                message = self.queue.get(True, self.timeout) 
     86                                provider.deliver(message) 
     87                         
     88                        except Empty: 
     89                                log.debug("Worker death from starvation.") 
     90                                break 
     91                         
     92                        except ProviderExhaustedException: 
     93                                log.debug("Worker death from provider exhaustion - spawning child.") 
     94                                self.deliver(message) 
     95                                self.spawn() 
     96                                break 
     97                         
     98                        except: 
     99                                log.exception("Delivery of message %s failed." % message.id) 
     100                                break 
     101                         
     102                        else: 
     103                                log.info("Delivery of message %s successful or deferred." % message.id) 
     104 
  • branches/3.0/turbomail/managers/immediate.py

    r43 r52  
    1818 
    1919class ImmediateManager(Manager): 
     20        name = "Immediate" 
     21        version = "1.0" 
     22        url = "http://www.python-turbomail.org/wiki/ImmediateManager" 
     23         
    2024        def __init__(self): 
    2125                log.info("Immediate manager starting up.") 
     
    2428                 
    2529                self.provider = None 
     30                 
     31                log.info("Immediate manager ready.") 
    2632                 
    2733        def deliver(self, message): 
     
    4551                        log.debug("Provider exhausted.") 
    4652                        self.provider = None 
     53                        self.deliver(message) 
    4754                 
    4855                except: 
  • branches/3.0/turbomail/message.py

    r41 r52  
    44 
    55import turbomail 
     6from turbomail import release 
    67from turbomail.util import AddressList 
    7 from turbomail.release import version 
    88import re, os, email 
    99 
     
    3131        This allows you to define your own message to be delivered.""" 
    3232         
    33         def __init__(self, sender=None, recipients=[], message=None): 
     33        def __init__(self, id=None, sender=None, recipients=[], message=None): 
     34                self.id = id 
    3435                self.sender = sender 
    3536                self.recipients = recipients 
    3637                self.message = message 
     38                 
     39                if not self.id: self.id = uuid() 
     40                 
     41                super(StubMessage, self).__init__() 
    3742         
    3843        def __str__(self): 
     
    5863                 
    5964                def configget(name, key, default=None): 
    60                         pass 
     65                        return kw.get(name, turbomail.config.get(key, default)) 
    6166                 
    6267                self.date = kw.get("date", formatdate(localtime=True)) 
    6368                 
    64                 self._senders = AddressList(kw.get("sender", turbomail.config.get("mail.message.sender", None))) 
    65                 self._senders = AddressList(kw.get("senders", turbomail.config.get("mail.message.senders", self._senders))) 
    66                 self._envelope = AddressList(kw.get("envelope", turbomail.config.get("mail.message.envelope", None))) 
    67                 self._reply = AddressList(kw.get("reply", turbomail.config.get("mail.message.reply", None))) 
     69                if 'authors' in kw: kw['author'] == kw['authors'] 
     70                if 'senders' in kw: kw['sender'] == kw['senders'] 
     71                 
     72                self._author = AddressList(configget("from", "mail.message.author")) 
     73                self._sender = AddressList(configget("sender", "mail.message.sender")) 
     74                self._reply = AddressList(configget("reply", "mail.message.reply")) 
    6875                self._to = AddressList(kw.get("to", None)) 
    69                 self._cc = AddressList(kw.get("cc", turbomail.config.get("mail.message.cc", None))) 
    70                 self._bcc = AddressList(kw.get("bcc", turbomail.config.get("mail.message.bcc", None))) 
    71                 self._disposition = AddressList(kw.get("disposition", turbomail.config.get("mail.message.disposition", None))) 
    72                  
    73                 self.organization = kw.get("organization", turbomail.config.get("mail.message.organization", None)) 
    74                 self.encoding = kw.get("encoding", turbomail.config.get("mail.encoding", 'us-ascii')) 
    75                 self.priority = kw.get("priority", turbomail.config.get("mail.message.priority", None)) 
     76                self._cc = AddressList(configget("cc", "mail.message.cc")) 
     77                self._bcc = AddressList(configget("bcc", "mail.message.bcc")) 
     78                self._disposition = AddressList(configget("disposition", "mail.message.disposition")) 
     79                 
     80                self.organization = configget("organization", "mail.message.organization") 
     81                self.encoding = configget("encoding", "mail.encoding", 'us-ascii') 
     82                self.priority = configget("priority", "mail.message.priority") 
    7683                self.subject = kw.get("subject", None) 
    7784                self.plain = kw.get("plain", None) 
     
    7986                self.attachments = kw.get("attachments", []) 
    8087                self.embedded = kw.get("embedded", []) 
    81                 self.headers = kw.get("headers", turbomail.config.get("mail.message.headers", [])) 
    82                 self.tries = kw.get("tries", turbomail.config.get("mail.tries", 3)) 
     88                self.headers = configget("headers", "mail.message.headers", []) 
     89                self.tries = configget("tries", "mail.tries", 3) 
    8390                 
    8491                self._id = kw.get("id", None) 
     
    94101                return self.mime.as_string() 
    95102         
    96         def _get_sender(self): 
    97                 if self._envelope: 
    98                         return self._envelope.addresses[0] 
    99                 return self._senders.addresses[0] 
    100          
    101         sender = AddressList.protected('_senders') 
    102         senders = AddressList.protected('_senders') 
    103         envelope = AddressList.protected('_envelope') 
     103        author = AddressList.protected('_author') 
     104        authors = AddressList.protected('_author') 
     105        sender = AddressList.protected('_sender') 
     106        senders = AddressList.protected('_sender') 
    104107        reply = AddressList.protected('_reply') 
    105108        to = AddressList.protected('_to') 
     
    114117                 
    115118                return self._id 
     119         
    116120        id = property(id) 
    117121         
     122        def envelope(self): 
     123                if self.sender and self.senders != self.author: 
     124                        return AddressList(self.senders) 
     125                return AddressList(self.author) 
     126         
     127        envelope = property(envelope) 
     128         
    118129        def recipients(self): 
    119                 return [isinstance(i, tuple) and i[1] or i for i in self.to + self.cc + self.bcc] 
     130                return AddressList(self.to + self.cc + self.bcc) 
     131         
    120132        recipients = property(recipients) 
    121133         
     
    123135                """Produce the final MIME message.""" 
    124136 
    125                 assert self.senders, "You must specify a sender." 
     137                assert self.author, "You must specify an author." 
    126138                assert self.subject, "You must specify a subject." 
    127139                assert self.to or self.cc or self.bcc, "You must specify at least one recipient." 
     
    134146                 
    135147                plain = MIMEText(self._callable(self.plain).encode(self.encoding), 'plain', self.encoding) 
    136                 rich = self.rich and MIMEText(self._callable(self.rich).encode(self.encoding), 'html', self.encoding) or None 
    137  
     148                 
     149                rich = None 
     150                if self.rich: 
     151                        rich = MIMEText(self._callable(self.rich).encode(self.encoding), 'html', self.encoding) 
     152                 
    138153                def generate_mime(): 
    139154                        if not rich: return plain 
    140  
     155                         
    141156                        message = MIMEMultipart('alternative') 
    142157                        message.attach(plain) 
    143  
     158                         
    144159                        if not self.embedded: 
    145160                                message.attach(rich) 
    146  
     161                         
    147162                        else: 
    148163                                embedded = MIMEMultipart('related') 
     
    150165                                for attachment in self.embedded: embedded.attach(attachment) 
    151166                                message.attach(embedded) 
    152  
     167                         
    153168                        return message 
    154  
     169                 
    155170                message = generate_mime() 
    156  
     171                 
    157172                if self.attachments: 
    158173                        attachments = MIMEMultipart() 
     
    160175                        for attachment in self.attachments: attachments.attach(attachment) 
    161176                        message = attachments 
    162  
     177                 
     178                if hasattr(self, "process_mime_message"): 
     179                        message = self.process_mime_message(message) 
     180                 
    163181                headers = [ 
    164                                 ('Sender', self.sender), # AddressList 
    165                                 ('From', self.senders), # AddressList 
    166                                 ('Reply-To', self.reply), # AddressList 
     182                                ('Sender', self.sender), 
     183                                ('From', self.author), 
     184                                ('Reply-To', self.reply), 
    167185                                ('Subject', self.subject), 
    168186                                ('Date', self.date), 
    169                                 ('To', self.to), # AddressList 
    170                                 ('Cc', self.cc), # AddressList 
    171                                 ('Disposition-Notification-To', self.disposition), # AddressList 
     187                                ('To', self.to), 
     188                                ('Cc', self.cc), 
     189                                ('Disposition-Notification-To', self.disposition), 
    172190                                ('Organization', self.organization), 
    173191                                ('X-Priority', self.priority), 
    174                                 ('X-Mailer', "TurboMail <http://www.python-turbomail.org/>"), 
    175                                 ('X-TurboMail-Version', version), 
    176                                 ('X-TurboMail-Message-GUID', self.id), 
    177                                 ('X-TurboMail-Extensions', "manager.demand v.1.0, provider.smtp v.1.0") 
    178192                        ] 
    179  
     193                 
     194                if turbomail.config.get("mail.brand", True): 
     195                        headers.extend([ 
     196                                        ('X-Mailer', "%s <%s>" % (release.name, release.url)), 
     197                                        ('X-TurboMail-Version', release.version), 
     198                                        ('X-TurboMail-Message-GUID', self.id), 
     199                                ]) 
     200                         
     201                        if turbomail.provider: 
     202                                headers.extend([ 
     203                                                ('X-TurboMail-Provider', "%s <%s>" % (turbomail.provider.name, turbomail.provider.url)), 
     204                                                ('X-TurboMail-Provider-Version', turbomail.provider.version), 
     205                                        ]) 
     206                         
     207                        if turbomail.manager: 
     208                                headers.extend([ 
     209                                                ('X-TurboMail-Manager', "%s <%s>" % (turbomail.manager.name, turbomail.manager.url)), 
     210                                                ('X-TurboMail-Manager-Version', turbomail.manager.version), 
     211                                        ]) 
     212                 
    180213                headers.extend(self.headers) 
    181  
     214                 
    182215                for header in headers: 
    183216                        if isinstance(header, (tuple, list)): 
     
    188221                        elif isinstance(header, dict): 
    189222                                message.add_header(**header) 
    190  
     223                 
    191224                self._mime = message 
    192225                self._processed = True 
     
    194227                 
    195228                return message 
     229         
    196230        mime = property(mime)<