Update of /cvsroot/tmda/tmda/TMDA
In directory usw-pr-cvs1:/tmp/cvs-serv9086/TMDA

Modified Files:
        ChangeLog Defaults.py Util.py 
Added Files:
        AutoResponse.py 
Log Message:
* Auto responder improvements:

- Auto responses are now MIME messages with one or two bodyparts,
depending on the value of AUTORESPONSE_INCLUDE_SENDER_COPY.

- Character sets other than US-ASCII in the templates are now
supported.  This includes multi-byte character sets such as those
found in Asian languages.  RFC-compliant charset conversion and 7-bit
transport encoding will be handled automatically by the auto
responder.

The default charset for the message body is US-ASCII, and can be
changed by editing `BodyCharset:' in your template.

- Multilingual header values are now supported, and likewise, this
includes multi-byte character sets.  The RFC 2047 encoding is handled
by the auto-responder.

The default charset for each header generated from a template is
US-ASCII.  You can change this by editing the .CHARSET suffix of each
header definition.  For example:

  Subject.US-ASCII: Please confirm your message

might be changed to 

  Subject.Latin-1: German translation here

if you wish to use German umlauts in your Subject field.

- The `Auto-Submitted:' header described in Keith Moore's IETF draft
``Recommendations for Automatic Responses to Electronic Mail''
(draft-moore-auto-email-response-00.txt) is supported.  This header is
checked when receiving incoming messages, and also added to TMDA's own
auto responses.


--- NEW FILE ---
# -*- python -*-
#
# Copyright (C) 2001,2002 Jason R. Mastaler <[EMAIL PROTECTED]>
#
# This file is part of TMDA.
#
# TMDA is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.  A copy of this license should
# be included in the file COPYING.
#
# TMDA is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with TMDA; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Manage a TMDA-style Auto Response."""


from email import message_from_string
from email.Header import Header, decode_header
from email.MIMEMessage import MIMEMessage
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Utils import formataddr, parseaddr

import os
import time

import Defaults
import Util
import Version


DEFAULT_CHARSET = 'US-ASCII'


class AutoResponse:
    def __init__(self, msgin, bouncetext, response_type, recipient):
        """
        msgin is an email.Message object representing the incoming
        message we are responding to.

        bouncetext is a string of rfc822 headers/body created from a
        TMDA template.

        response_type is the type of auto response we should send
        ('request' is a confirmation request, 'accept' is a
        confirmation acceptance notice, and 'bounce' is a failure
        notice).

        recipient is the recipient e-mail address of this auto
        response.  Normally the envelope sender address.
        """
        self.msgin = msgin
        self.msgin_as_string = self.msgin.as_string()
        self.msgin_size = len(self.msgin_as_string)
        self.bouncemsg = message_from_string(bouncetext)
        self.responsetype = response_type
        self.recipient = recipient
        self.bodycharset = self.bouncemsg.get('bodycharset')
        if self.bodycharset is None:
            self.bodycharset = DEFAULT_CHARSET


    def create(self):
        """
        Create an auto response object from whole cloth.

        The auto response is a MIME compliant entity with either one
        or two bodyparts, depending on what
        Defaults.AUTORESPONSE_INCLUDE_SENDER_COPY is set to.

        In most cases, the object will look like:

        multipart/mixed
                text/plain (response text)
                message/rfc822 or text/rfc822-headers (sender's message)
        """
        # Headers that users shouldn't be setting in their templates.
        bad_headers = ['MIME-Version', 'Content-Type', 'BodyCharset',
                       'Content-Transfer-Encoding', 'Content-Disposition']
        for h in bad_headers:
            if self.bouncemsg.has_key(h):
                del self.bouncemsg[h]
        textpart = MIMEText(self.bouncemsg.get_payload(), 'plain',
                            self.bodycharset)
        bodyparts = 1 + Defaults.AUTORESPONSE_INCLUDE_SENDER_COPY
        if bodyparts == 1:
            # A single text/plain entity.
            self.mimemsg = textpart
        elif bodyparts > 1:
            # A multipart/mixed entity with two bodyparts.
            self.mimemsg = MIMEMultipart('mixed')
            self.mimemsg.attach(textpart)
            if Defaults.AUTORESPONSE_INCLUDE_SENDER_COPY == 1:
                # include the headers only as a text/rfc822-headers part.
                rfc822part = MIMEText(self.msgin_as_string,
                                      'rfc822-headers',
                                      self.msgin.get_charsets()[0])
            elif Defaults.AUTORESPONSE_INCLUDE_SENDER_COPY == 2:
                # include the entire message as a message/rfc822 part.
                # don't include the payload if it's over a certain size.
                if (Defaults.CONFIRM_MAX_MESSAGE_SIZE and
                    (int(Defaults.CONFIRM_MAX_MESSAGE_SIZE) < int(self.msgin_size))):
                    new_payload = '[ Message body suppressed (exceeded %s bytes) ]' \
                                  % Defaults.CONFIRM_MAX_MESSAGE_SIZE
                    self.msgin.set_payload(new_payload)
                rfc822part = MIMEMessage(self.msgin)
            self.mimemsg.attach(rfc822part)
        # fold the template headers into the main entity.
        for k, v in self.bouncemsg.items():
            ksplit = k.split('.', 1)
            if len(ksplit) == 1:
                hdrcharset = DEFAULT_CHARSET
            else:
                # Header.CHARSET: Value
                k = ksplit[0]
                hdrcharset = ksplit[1]
            # headers like `From:' which contain e-mail addresses
            # might need the "Fullname" portion encoded, but the
            # address portion must _not_ be encoded.
            if k.lower() in map(lambda s: s.lower(),
                                Defaults.AUTORESPONSE_TEMPLATE_EMAIL_HEADERS):
                name, addr = parseaddr(v)
                if name:
                    h = Header(name, hdrcharset)
                    name = h.encode()
                self.mimemsg[k] = formataddr((name, addr))
            # headers like `Subject:' might contain an encoded string,
            # so we need to decode that first before encoding the
            # entire header value.
            elif k.lower() in map(lambda s: s.lower(),
                                  Defaults.AUTORESPONSE_TEMPLATE_ENCODED_HEADERS):
                h = Header(charset=hdrcharset, header_name=k)
                decoded_seq = decode_header(v)
                for s, charset in decoded_seq:
                    h.append(s, charset)
                self.mimemsg[k] = h
            else:
                self.mimemsg[k] = Header(v, hdrcharset, header_name=k)
        # Add some new headers to the main entity.
        timesecs = time.time()
        self.mimemsg['Date'] = Util.make_date(timesecs) # required by RFC 2822
        self.mimemsg['Message-ID'] = Util.make_msgid(timesecs) # Ditto
        # References
        refs = []
        for h in ['references', 'message-id']:
            if self.msgin.has_key(h):
                refs = refs + self.msgin.get(h).split()
        if refs:
            self.mimemsg['References'] = '\n\t'.join(refs)
        # In-Reply-To
        if self.msgin.has_key('message-id'):
            self.mimemsg['In-Reply-To'] =  self.msgin.get('message-id')
        self.mimemsg['To'] = self.recipient
        # Some auto responders respect this header.
        self.mimemsg['Precedence'] = 'bulk'
        # Auto-Submitted per draft-moore-auto-email-response-00.txt
        if self.responsetype in ('request', 'accept'):
            self.mimemsg['Auto-Submitted'] = 'auto-replied'
        elif self.responsetype == 'bounce':
            self.mimemsg['Auto-Submitted'] = 'auto-generated (failure)'
        self.mimemsg['X-Delivery-Agent'] = 'TMDA/%s' % Version.TMDA
        # Optionally, add some user-specified headers.
        if Defaults.ADDED_HEADERS_SERVER:
            for hdr in Defaults.ADDED_HEADERS_SERVER.keys():
                del self.mimemsg[hdr]
                self.mimemsg[hdr] = Defaults.ADDED_HEADERS_SERVER[hdr]


    def send(self):
        """
        Inject the auto response into the mail transport system.
        """
        Util.sendmail(self.mimemsg.as_string(),
                      self.recipient, Defaults.BOUNCE_ENV_SENDER)


    def record(self):
        """
        Record this auto response.  Used as part of TMDA's auto
        response rate limiting feature, controlled by
        Defaults.MAX_AUTORESPONSES_PER_DAY.
        """
        response_filename = '%s.%s.%s' % (int(time.time()),
                                          Defaults.PID,
                                          Util.normalize_sender(self.recipient))
        # Create ~/.tmda/responses if necessary.
        if not os.path.exists(Defaults.RESPONSE_DIR):
            os.makedirs(Defaults.RESPONSE_DIR, 0700)
        fp = open(os.path.join(Defaults.RESPONSE_DIR,
                               response_filename), 'w')
        fp.close()


Index: ChangeLog
===================================================================
RCS file: /cvsroot/tmda/tmda/TMDA/ChangeLog,v
retrieving revision 1.233
retrieving revision 1.234
diff -u -r1.233 -r1.234
--- ChangeLog   4 Oct 2002 23:43:31 -0000       1.233
+++ ChangeLog   18 Oct 2002 22:36:16 -0000      1.234
@@ -1,3 +1,15 @@
+2002-10-18  Jason R. Mastaler  <[EMAIL PROTECTED]>
+
+       * Util.py (normalize_sender): New fuction, moved from
+       tmda-rfilter.
+       
+       * Defaults.py (AUTORESPONSE_INCLUDE_SENDER_COPY): New variable.
+
+       (AUTORESPONSE_TEMPLATE_EMAIL_HEADERS): Ditto.
+       (AUTORESPONSE_TEMPLATE_ENCODED_HEADERS): Ditto.
+
+       * AutoResponse.py: New file.
+
 2002-10-04  Jason R. Mastaler  <[EMAIL PROTECTED]>
 
        * Defaults.py (MESSAGE_TAG_HEADER_STYLE): New variable.

Index: Defaults.py
===================================================================
RCS file: /cvsroot/tmda/tmda/TMDA/Defaults.py,v
retrieving revision 1.146
retrieving revision 1.147
diff -u -r1.146 -r1.147
--- Defaults.py 4 Oct 2002 23:43:31 -0000       1.146
+++ Defaults.py 18 Oct 2002 22:36:16 -0000      1.147
@@ -919,6 +919,43 @@
 if not vars().has_key('RESPONSE_DIR') and MAX_AUTORESPONSES_PER_DAY != 0:
     RESPONSE_DIR = os.path.join(DATADIR, 'responses')
 
+# AUTORESPONSE_INCLUDE_SENDER_COPY
+# An integer which controls whether a copy of the sender's message is
+# included or not when sending an auto response.  Available options:
+#
+# 0 - do not include any portion of the sender's message.
+# 1 - include only the headers from the sender's message.
+# 2 - include the sender's entire message (recommended).
+#
+# Default is 2
+if not vars().has_key('AUTORESPONSE_INCLUDE_SENDER_COPY'):
+    AUTORESPONSE_INCLUDE_SENDER_COPY = 2
+
+# AUTORESPONSE_TEMPLATE_EMAIL_HEADERS
+# A list containing the names of headers in your templates that
+# contain an e-mail address.  This is necessary so that the e-mail
+# address will avoid being RFC 2047 encoded when handling
+# internationalized headers.
+#
+# Example:
+# AUTORESPONSE_TEMPLATE_EMAIL_HEADERS = ["from", "reply-to"]
+#
+# Default is "From:" and "Reply-To:".
+if not vars().has_key('AUTORESPONSE_TEMPLATE_EMAIL_HEADERS'):
+    AUTORESPONSE_TEMPLATE_EMAIL_HEADERS = ['from', 'reply-to']
+
+# AUTORESPONSE_TEMPLATE_ENCODED_HEADERS
+# A list containing the names of headers in your templates that might
+# contain an RFC 2047 encoded string.  This is necessary so that they
+# can be decoded first when handling internationalized headers.
+#
+# Example:
+# AUTORESPONSE_TEMPLATE_ENCODED_HEADERS = ["subject"]
+#
+# Default is "Subject:".
+if not vars().has_key('AUTORESPONSE_TEMPLATE_ENCODED_HEADERS'):
+    AUTORESPONSE_TEMPLATE_ENCODED_HEADERS = ['subject']
+
 # DELIVERED_CACHE
 # Path to the cache file used to keep track of which messages have
 # already been delivered.

Index: Util.py
===================================================================
RCS file: /cvsroot/tmda/tmda/TMDA/Util.py,v
retrieving revision 1.70
retrieving revision 1.71
diff -u -r1.70 -r1.71
--- Util.py     30 Sep 2002 23:45:54 -0000      1.70
+++ Util.py     18 Oct 2002 22:36:16 -0000      1.71
@@ -348,6 +348,20 @@
     os.spawnvp(os.P_WAIT, pager_list[0], pager_list)
 
 
+def normalize_sender(sender):
+    """Return a normalized version of the given sender address for use
+    in ~/.tmda/responses.
+
+    - Any / characters are replaced with : to prevent creation of files
+      outside the directory.
+    - Spaces are replaced with underscores.
+    - The address is lowercased.
+    """
+    sender = sender.replace(' ', '_')
+    sender = sender.replace('/', ':')
+    return sender.lower()
+
+
 def sendmail(msgstr, envrecip, envsender):
     """Send e-mail via direct SMTP, or by opening a pipe to the
     sendmail program.

_______________________________________
tmda-cvs mailing list
http://tmda.net/lists/listinfo/tmda-cvs

Reply via email to