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