summaryrefslogtreecommitdiffstats
path: root/tcllib/examples/mime
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/examples/mime
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/examples/mime')
-rw-r--r--tcllib/examples/mime/maildemo.tcl78
-rw-r--r--tcllib/examples/mime/mbot/ChangeLog10
-rw-r--r--tcllib/examples/mime/mbot/README.html817
-rw-r--r--tcllib/examples/mime/mbot/README.txt1008
-rw-r--r--tcllib/examples/mime/mbot/README.xml720
-rwxr-xr-xtcllib/examples/mime/mbot/impersonal.tcl531
-rw-r--r--tcllib/examples/mime/mbot/mbox.tcl465
-rw-r--r--tcllib/examples/mime/mbot/mutl.tcl123
-rwxr-xr-xtcllib/examples/mime/mbot/personal.tcl982
-rw-r--r--tcllib/examples/mime/mbot/pkgIndex.tcl3
10 files changed, 4737 insertions, 0 deletions
diff --git a/tcllib/examples/mime/maildemo.tcl b/tcllib/examples/mime/maildemo.tcl
new file mode 100644
index 0000000..56fe98b
--- /dev/null
+++ b/tcllib/examples/mime/maildemo.tcl
@@ -0,0 +1,78 @@
+#!/usr/bin/env tclsh
+## -*- tcl -*-
+# maildemo.tcl - Copyright (C) 2005 Pat Thoyts <patthoyts@users.sf.net>
+#
+# This program illustrates the steps required to compose a MIME message and
+# mail it to a recipient using the tcllib mime and smtp packages.
+#
+# If we can find suitable environment variables we will authenticate with a
+# server (if it presents this option) and we will use SSL communications
+# if available.
+#
+# $Id: maildemo.tcl,v 1.3 2010/11/25 17:19:45 andreas_kupries Exp $
+
+package require mime
+package require smtp
+
+# The use of SSL by our client can be controlled by a policy procedure. Using
+# this we can specify that we REQUIRE SSL or we can make SSL optional.
+# This procedure should return 'secure' to require SSL
+#
+proc policy {demoarg code diagnostic} {
+ if {$code > 299} {
+ puts stderr "TLS error: $code $diagnostic"
+ }
+ #return secure; # fail if no TLS
+ return insecure;
+}
+
+# Setup default sender and target
+set DEFUSER tcllib-demo@[info host]
+set USERNAME $tcl_platform(user)
+set PASSWORD ""
+
+# Try and lift authentication details from the environment.
+if {[info exists env(USERNAME)]} {
+ set USERNAME $env(USERNAME)
+}
+
+# We can get the password from http_proxy_pass - maybe.
+if {[info exists env(http_proxy_pass)]} {
+ set PASSWORD $env(http_proxy_pass)
+}
+
+set defmsg "This is a default tcllib demo mail message."
+
+# Compose and send a message. Command parameters can override the settings
+# discovered above.
+#
+proc Send [list \
+ [list server localhost] \
+ [list port 25] \
+ [list from $DEFUSER] \
+ [list to $DEFUSER] \
+ [list msg $defmsg]] {
+ set tok [mime::initialize -canonical text/plain -string $msg]
+ set args [list \
+ -debug 1 \
+ -servers [list $server] \
+ -ports [list $port] \
+ -usetls 1 \
+ -tlspolicy [list policy $tok] \
+ -header [list From "$from"] \
+ -header [list To "$to"] \
+ -header [list Subject "RFC 2554 test"] \
+ -header [list Date "[clock format [clock seconds]]"]]
+ if {[info exists ::USERNAME] && [string length $::USERNAME] > 0} {
+ lappend args \
+ -username $::USERNAME \
+ -password $::PASSWORD
+ }
+
+ eval [linsert $args 0 smtp::sendmessage $tok]
+ mime::finalize $tok
+}
+
+if {!$tcl_interactive} {
+ eval [linsert $argv 0 Send]
+}
diff --git a/tcllib/examples/mime/mbot/ChangeLog b/tcllib/examples/mime/mbot/ChangeLog
new file mode 100644
index 0000000..f517447
--- /dev/null
+++ b/tcllib/examples/mime/mbot/ChangeLog
@@ -0,0 +1,10 @@
+2007-03-23 Andreas Kupries <andreask@activestate.com>
+
+ * pkgIndex.tcl: Added MD hints.
+
+2004-09-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * impersonal.tcl: Fixed 'expr'essions without braces.
+ * mbox.tcl:
+ * mutl.tcl:
+ * personal.tcl:
diff --git a/tcllib/examples/mime/mbot/README.html b/tcllib/examples/mime/mbot/README.html
new file mode 100644
index 0000000..ec98b12
--- /dev/null
+++ b/tcllib/examples/mime/mbot/README.html
@@ -0,0 +1,817 @@
+<html><head><title>The README file: The personal.tcl Mailbot</title>
+<meta http-equiv="Expires" content="Wed, 14 Aug 2002 20:43:57 +0000">
+<STYLE type='text/css'>
+ .title { color: #990000; font-size: 22px; line-height: 22px; font-weight: bold; text-align: right;
+ font-family: helvetica, arial, sans-serif }
+ .filename { color: #666666; font-size: 18px; line-height: 28px; font-weight: bold; text-align: right;
+ font-family: helvetica, arial, sans-serif }
+ p.copyright { color: #000000; font-size: 10px;
+ font-family: verdana, charcoal, helvetica, arial, sans-serif }
+ p { margin-left: 2em; margin-right: 2em; }
+ li { margin-left: 3em; }
+ ol { margin-left: 2em; margin-right: 2em; }
+ ul.text { margin-left: 2em; margin-right: 2em; }
+ pre { margin-left: 3em; color: #333333 }
+ ul.toc { color: #000000; line-height: 16px;
+ font-family: verdana, charcoal, helvetica, arial, sans-serif }
+ H3 { color: #333333; font-size: 16px; line-height: 16px; font-family: helvetica, arial, sans-serif }
+ H4 { color: #000000; font-size: 14px; font-family: helvetica, arial, sans-serif }
+ TD.header { color: #ffffff; font-size: 10px; font-family: arial, helvetica, san-serif; valign: top }
+ TD.author-text { color: #000000; font-size: 10px;
+ font-family: verdana, charcoal, helvetica, arial, sans-serif }
+ TD.author { color: #000000; font-weight: bold; margin-left: 4em; font-size: 10px; font-family: verdana, charcoal, helvetica, arial, sans-serif }
+ A:link { color: #990000; font-weight: bold;
+ font-family: MS Sans Serif, verdana, charcoal, helvetica, arial, sans-serif }
+ A:visited { color: #333333; font-weight: bold;
+ font-family: MS Sans Serif, verdana, charcoal, helvetica, arial, sans-serif }
+ A:name { color: #333333; font-weight: bold;
+ font-family: MS Sans Serif, verdana, charcoal, helvetica, arial, sans-serif }
+ .link2 { color:#ffffff; font-weight: bold; text-decoration: none;
+ font-family: monaco, charcoal, geneva, MS Sans Serif, helvetica, monotype, verdana, sans-serif;
+ font-size: 9px }
+ .RFC { color:#666666; font-weight: bold; text-decoration: none;
+ font-family: monaco, charcoal, geneva, MS Sans Serif, helvetica, monotype, verdana, sans-serif;
+ font-size: 9px }
+ .hotText { color:#ffffff; font-weight: normal; text-decoration: none;
+ font-family: charcoal, monaco, geneva, MS Sans Serif, helvetica, monotype, verdana, sans-serif;
+ font-size: 9px }
+</style>
+</head>
+<body bgcolor="#ffffff" text="#000000" alink="#000000" vlink="#666666" link="#990000">
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<table width="66%" border="0" cellpadding="0" cellspacing="0"><tr><td><table width="100%" border="0" cellpadding="2" cellspacing="1">
+<tr valign="top"><td width="33%" bgcolor="#666666" class="header">The README file</td><td width="33%" bgcolor="#666666" class="header">M. Rose</td></tr>
+<tr valign="top"><td width="33%" bgcolor="#666666" class="header">&nbsp;</td><td width="33%" bgcolor="#666666" class="header">Dover Beach Consulting, Inc.</td></tr>
+<tr valign="top"><td width="33%" bgcolor="#666666" class="header">&nbsp;</td><td width="33%" bgcolor="#666666" class="header">February 2002</td></tr>
+</table></td></tr></table>
+<div align="right"><font face="monaco, MS Sans Serif" color="#990000" size="+3"><b><br><span class="title">The personal.tcl Mailbot</span></b></font></div>
+<font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<h3>Abstract</h3>
+
+<p>The personal.tcl mailbot implements a highly-specialized
+filter for personal messages.
+It MUST not be used by people who receive mailing list traffic in
+their personal mailboxes.
+</p><a name="toc"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<h3>Table of Contents</h3>
+<ul compact class="toc">
+<b><a href="#anchor1">1.</a>&nbsp;
+SYNOPSIS<br></b>
+<b><a href="#anchor2">1.1</a>&nbsp;
+Requirements<br></b>
+<b><a href="#anchor3">1.2</a>&nbsp;
+Copyrights<br></b>
+<b><a href="#anchor4">2.</a>&nbsp;
+PHILOSOPHY<br></b>
+<b><a href="#anchor5">2.1</a>&nbsp;
+Guest Lists<br></b>
+<b><a href="#anchor6">3.</a>&nbsp;
+BEHAVIOR<br></b>
+<b><a href="#anchor7">3.1</a>&nbsp;
+Arguments<br></b>
+<b><a href="#actions">3.2</a>&nbsp;
+Actions<br></b>
+<b><a href="#configFile">3.3</a>&nbsp;
+The Configuration File<br></b>
+<b><a href="#options">3.3.1</a>&nbsp;
+Configuration Options<br></b>
+<b><a href="#procs">3.3.2</a>&nbsp;
+Configurable Procedures<br></b>
+<b><a href="#rfc.references1">&#167;</a>&nbsp;
+References<br></b>
+<b><a href="#rfc.authors">&#167;</a>&nbsp;
+Author's Address<br></b>
+<b><a href="#impersonal">A.</a>&nbsp;
+Impersonal Mail<br></b>
+<b><a href="#impersonal.options">A.1</a>&nbsp;
+Configuration Options<br></b>
+<b><a href="#options.foldersDirectory">A.1.1</a>&nbsp;
+foldersDirectory<br></b>
+<b><a href="#options.foldersFile">A.1.2</a>&nbsp;
+foldersFile<br></b>
+<b><a href="#options.announceMailboxes">A.1.3</a>&nbsp;
+announceMailboxes<br></b>
+<b><a href="#options.mappingFile">A.1.4</a>&nbsp;
+mappingFile<br></b>
+<b><a href="#impersonal.procs">A.2</a>&nbsp;
+Configurable Procedures<br></b>
+<b><a href="#procs.impersonalMail">A.2.1</a>&nbsp;
+impersonalMail<br></b>
+<b><a href="#procs.processFolder">A.2.2</a>&nbsp;
+processFolder<br></b>
+<b><a href="#anchor8">B.</a>&nbsp;
+An Example configFile<br></b>
+<b><a href="#anchor9">C.</a>&nbsp;
+Acknowledgements<br></b>
+</ul>
+<br clear="all">
+
+<a name="anchor1"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.1"></a><h3>1.&nbsp;SYNOPSIS</h3>
+
+<p>Create a <a href="#configFile">configuration file</a>
+and add this line to your ".forward" file:
+</p></font><pre>
+ "| LIB/mbot-1.1/personal.tcl -config FILE -user USER"
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>where "LIB" is where the Tcl library lives,
+"FILE" is the name of your configuration file,
+and "USER" is your username.
+</p>
+<a name="rfc.section.1.1"></a><h4><a name="anchor2">1.1</a>&nbsp;Requirements</h4>
+
+<p>This package requires:
+
+<ul class="text">
+<li><a href="http://core.tcl.tk/tcl/">Tcl version 8.3</a>
+or later
+</li>
+<li><a href="http://core.tcl.tk/tcllib/">tcl lib</a>
+</li>
+<li><a href="http://sourceforge.net/projects/tclx/">TclX version 8.0</a>
+or later
+</li>
+</ul><p>
+</p>
+<a name="rfc.section.1.2"></a><h4><a name="anchor3">1.2</a>&nbsp;Copyrights</h4>
+
+<p>(c) 1999-2002 Marshall T. Rose
+</p>
+<p>Hold harmless the author, and any lawful use is allowed.
+</p>
+<a name="anchor4"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.2"></a><h3>2.&nbsp;PHILOSOPHY</h3>
+
+<p>The mailbot's philosophy is simple:
+
+<ul class="text">
+<li>The mailbot receives all of your incoming personal mail.
+</li>
+<li>You ALWAYS copy yourself on every message you send,
+so that the mailbot receives all of your outgoing personal mail.
+</li>
+<li>The mailbot performs six tasks, all optional:
+
+<ul class="text">
+<li>makes audit copies of your incoming and outgoing mail;
+</li>
+<li>performs duplicate supression;
+</li>
+<li>performs originator supression by rejecting messages from people
+who aren't your friends or on a guest list;
+</li>
+<li>performs content supression by rejecting messages that contain
+attachments with extensions on your prohibited list;
+</li>
+<li>sends a textual synopsis to your PDA; and,
+</li>
+<li>sends a copy to your remote mailbox.
+</li>
+</ul><p>
+</li>
+</ul><p>
+</p>
+<p>Do NOT use the personal.tcl mailbot if you receive mailing list
+traffic in your personal mailbox.
+When sending mail to a mailing list,
+either:
+
+<ul class="text">
+<li>use a "From" address that the personal.tcl mailbot will process as
+"impersonal" mail,
+(e.g., "hewes+ietf.general@example.com"); or,
+</li>
+<li>set the "Reply-To" for the message to the mailing list.
+</li>
+</ul><p>
+Consult <a href="#impersonal">Impersonal Mail</a> for information on how
+"impersonal" mail is identified and processed.
+</p>
+<a name="rfc.section.2.1"></a><h4><a name="anchor5">2.1</a>&nbsp;Guest Lists</h4>
+
+<p>Guest lists are an effective mechanism for cutting back on
+excessive mail.
+
+<ul class="text">
+<li>when the mailbot receives a message from you,
+it adds any recipients it finds to a permanent-guest list;
+</li>
+<li>when the mailbot receives a message from someone on a guest list,
+it adds any recipients it finds to a temporary-guest list; but,
+</li>
+<li>when the mailbot receives a message from someone not on any guest
+list,
+they get a rejection notice.
+</li>
+</ul><p>
+Note that in order to promote someone to the permanent-guest list,
+you must send them a message (with a copy to yourself).
+In most cases,
+simply replying to the original message accomplishes this.
+Of course,
+if you don't want to promote someone to the permanent-guest list,
+simply remove that address (or your address) from the list of
+recipients in your reply.
+</p>
+<p>Here are the fine points:
+
+<ul class="text">
+<li>rejection notices contain a passphrase that may be used at most
+once to bypass the guest list mechanism
+(notices also contain the original message to minimize type-in
+by the uninvited);
+</li>
+<li>a flip-flop is used to avoid mail loops; and,
+</li>
+<li>messages originated by an administrative address (e.g.,
+"Postmaster") bypass the guest list mechanism
+(unless the message refers to a previously-rejected message,
+in which case it is supressed).
+</li>
+</ul><p>
+</p>
+<p>The rejection notice should be written carefully to minimize an
+extreme negative reaction on the part of the uninvited.
+Of course,
+by allowing a passphrase,
+this provides something of a CQ test for the uninvited --
+if someone can't pass the test...
+</p>
+<a name="anchor6"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.3"></a><h3>3.&nbsp;BEHAVIOR</h3>
+
+<a name="rfc.section.3.1"></a><h4><a name="anchor7">3.1</a>&nbsp;Arguments</h4>
+
+<p>The mailbot supports the following command line arguments:
+
+<blockquote class="text"><dl>
+<dt> -config configFile:</dt>
+<dd>
+specifies the name of the configuration file to use;
+</dd>
+<dt> -debug boolean:</dt>
+<dd>
+enables debug output;
+</dd>
+<dt> -file messageFile:</dt>
+<dd>
+specifies the name of the file containing the message;
+</dd>
+<dt> -originator orginatorAddress:</dt>
+<dd>
+specifies the email-address of the originator of the message; and,
+</dd>
+<dt> -user userName:</dt>
+<dd>
+specifies the user-identity of the recipient.
+</dd>
+</dl></blockquote><p>
+Note that if "-user" is given,
+then the working directory is set to userName's home directory before
+configFile is sourced,
+and the umask is set defensively.
+</p>
+<p>The default values are:
+</p></font><pre>
+ personal.tcl -config .personal-config.tcl \
+ -debug 0 \
+ -file - \
+ -originator "derived from message"
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Given the default values,
+only "-user" need be specified.
+The reason is that if a message is being delivered to multiple local
+recipients,
+and if any of the ".forward" files are identical in content,
+then sendmail may not deliver the message to all of the local
+recipients.
+</p>
+<p>A few other (sendmail related) tips:
+
+<ul class="text">
+<li>If sendmail is configured with smrsh,
+you'll need to symlink personal.tcl into the
+/usr/libexec/sm.bin/ directory.
+</li>
+<li>Make sure that tclsh8.0 is in the path specified on the third-line
+of personal.tcl.
+</li>
+<li>You should chmod your ".forward" file to 0600.
+</li>
+</ul><p>
+</p>
+<a name="rfc.section.3.2"></a><h4><a name="actions">3.2</a>&nbsp;Actions</h4>
+
+<p>The mailbot begins by parsing its arguments,
+sourcing configFile,
+and then examining the incoming message:
+
+<ol class="text">
+<li>If <a href="#options.auditInFile">auditInFile</a> is set,
+a copy of the message is
+<a href="#procs.saveMessage">saved</a> there.
+</li>
+<li>If the message contains a previously-encountered "Message-ID",
+processing terminates.
+</li>
+<li>If the message's originator can not be determined,
+a copy of the message is
+<a href="#procs.saveMessage">saved</a> in the
+<a href="#options.defaultMaildrop">defaultMaildrop</a> and
+processing terminates.
+</li>
+<li>The originator's email-address is examined:
+
+<ol class="text">
+<li>If the originator appears to be an
+<a href="#procs.adminP">automated administrative process</a>,
+and if a previously rejected email-address is found in the message,
+processing terminates.
+</li>
+<li>Otherwise,
+if the originator isn't <a href="#procs.ownerP">the user</a>,
+or <a href="#procs.friendP">a friend</a>,
+or a permanent-access guest,
+or a temporary-access guest,
+and if <a href="#options.noticeFile">noticeFile</a> is set,
+then the message is rejected.
+</li>
+<li>Otherwise,
+each recipient email-address in the message's header is added to a guest
+list.
+(If the originator is <a href="#procs.ownerP">the user</a>,
+the permanent-guest list is used instead of the temporary-guest
+list.)
+</li>
+</ol><p>
+</li>
+<li>If the originator is the <a href="#procs.ownerP">the user</a>,
+then:
+
+<ol class="text">
+<li>If <a href="#options.auditOutFile">auditOutFile</a> is set,
+<a href="#procs.saveMessage">saved</a> there.
+</li>
+<li>Regardless, processing terminates.
+</li>
+</ol><p>
+</li>
+<li>If <a href="#options.pdaMailboxes">pdaMailboxes</a> is set,
+and if any plaintext is contained in the message,
+then the plaintext is sent to those email-addresses.
+</li>
+<li>If <a href="#options.remoteMailboxes">remoteMailboxes</a> is set,
+and if the message is successful resent to those email-addresses,
+then processing terminates.
+</li>
+<li>A copy of the message is
+<a href="#procs.saveMessage">saved</a> in the
+<a href="#options.defaultMaildrop">defaultMaildrop</a> and
+processing terminates.
+</li>
+</ol><p>
+</p>
+<a name="rfc.section.3.3"></a><h4><a name="configFile">3.3</a>&nbsp;The Configuration File</h4>
+
+<p>There are two kinds of information that may be defined in configFile:
+<a href="#options">configuration options</a> and
+<a href="#procs">configurable procedures</a>.
+</p>
+<p>Here's a simple example of a configFile for a user named
+"example":
+</p></font><pre>
+ set options(dataDirectory) .personal
+ set options(defaultMaildrop) /var/mail/example
+ set options(logFile) [file join .personal personal.log]
+ set options(noticeFile) [file join .personal notice.txt]
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<a name="rfc.section.3.3.1"></a><h4><a name="options">3.3.1</a>&nbsp;Configuration Options</h4>
+
+<p>configFile must define
+<a href="#options.dataDirectory">dataDirectory</a>
+and
+<a href="#options.defaultMaildrop">defaultMaildrop</a>.
+All other configuration options are optional.
+</p>
+<a name="rfc.section.3.3.1.1"></a><h4><a name="options.dataDirectory">3.3.1.1</a>&nbsp;dataDirectory</h4>
+
+<p>The directory where the mailbot keeps its databases.
+The subdirectories are:
+
+<blockquote class="text"><dl>
+<dt> badaddrs:</dt>
+<dd>the directory of rejected email-addresses
+</dd>
+<dt> inaddrs:</dt>
+<dd>the directory of originator email-addresses
+</dd>
+<dt> msgids:</dt>
+<dd>the directory of Message-IDs
+</dd>
+<dt> outaddrs:</dt>
+<dd>the permanent-guest list
+</dd>
+<dt> phrases:</dt>
+<dd>the directory of at-most-once passphrases
+</dd>
+<dt> tmpaddrs:</dt>
+<dd>the temporary-guest list
+</dd>
+</dl></blockquote><p>
+If you want to remove someone from a guest list,
+simply go to that directory and delete the corresponding file.
+</p>
+<a name="rfc.section.3.3.1.2"></a><h4><a name="options.defaultMaildrop">3.3.1.2</a>&nbsp;defaultMaildrop</h4>
+
+<p>The filename where messages are
+<a href="#procs.saveMessage">saved</a> for later viewing by
+your user agent.
+</p>
+<a name="rfc.section.3.3.1.3"></a><h4><a name="options.auditInFile">3.3.1.3</a>&nbsp;auditInFile</h4>
+
+<p>The filename where messages are
+<a href="#procs.saveMessage">saved</a> for audit purposes.
+</p>
+<a name="rfc.section.3.3.1.4"></a><h4><a name="options.auditOutFile">3.3.1.4</a>&nbsp;auditOutFile</h4>
+
+<p>The filename where your outgoing messages are
+<a href="#procs.saveMessage">saved</a> for audit purposes.
+</p>
+<a name="rfc.section.3.3.1.5"></a><h4><a name="options.dropNames">3.3.1.5</a>&nbsp;dropNames</h4>
+
+<p>A list of filename extensions for attachments that automatically
+cause the message to be rejected.
+</p>
+<a name="rfc.section.3.3.1.6"></a><h4><a name="options.friendlyDomains">3.3.1.6</a>&nbsp;friendlyDomains</h4>
+
+<p>A list used by <a href="#procs.friendP">friendP</a> giving
+the domain names where your friends live.
+</p>
+<a name="rfc.section.3.3.1.7"></a><h4><a name="options.friendlyfire">3.3.1.7</a>&nbsp;friendlyfire</h4>
+
+<p>If present and true,
+then someone sending a message both to you and someone you've
+previously sent mail to,
+is considered a friend.
+</p>
+<a name="rfc.section.3.3.1.8"></a><h4><a name="options.logFile">3.3.1.8</a>&nbsp;logFile</h4>
+
+<p>The filename where the mailbot
+<a href="#procs.tclLog">logs</a> its actions.
+</p>
+<a name="rfc.section.3.3.1.9"></a><h4><a name="options.myMailbox">3.3.1.9</a>&nbsp;myMailbox</h4>
+
+<p>Your preferred email-address with commentary text, e.g.,
+</p></font><pre>
+ Arlington Hewes &lt;hewes@example.com>
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<a name="rfc.section.3.3.1.10"></a><h4><a name="options.noticeFile">3.3.1.10</a>&nbsp;noticeFile</h4>
+
+<p>The filename containing the textual notice sent when a message is
+rejected.
+Note that all occurrances of "%passPhrase%" within this file are
+replaced with an at-most-once passphrase allowing the originator to
+bypass the mailbot's filtering.
+Similarly,
+any occurrences of "%subject%" are replaced by the "Subject" of the
+incoming message.
+</p>
+<a name="rfc.section.3.3.1.11"></a><h4><a name="options.pdaMailboxes">3.3.1.11</a>&nbsp;pdaMailboxes</h4>
+
+<p>The email-addresses where a textual synopsis of the incoming message is
+sent.
+</p>
+<a name="rfc.section.3.3.1.12"></a><h4><a name="options.remoteMailboxes">3.3.1.12</a>&nbsp;remoteMailboxes</h4>
+
+<p>The email-addresses where a copy of the incoming message is resent.
+</p>
+<a name="rfc.section.3.3.2"></a><h4><a name="procs">3.3.2</a>&nbsp;Configurable Procedures</h4>
+
+<p>All of these procedures are defined in personal.tcl.
+You may override any of them in configFile.
+</p>
+<a name="rfc.section.3.3.2.1"></a><h4><a name="procs.adminP">3.3.2.1</a>&nbsp;adminP</h4>
+</font><pre>
+ proc adminP {local domain}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Returns "1" if the email-address is an automated administrative
+process.
+</p>
+<a name="rfc.section.3.3.2.2"></a><h4><a name="procs.friendP">3.3.2.2</a>&nbsp;friendP</h4>
+</font><pre>
+ proc friendP {local domain}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Returns "1" if the email-address is from a
+<a href="#options.friendlyDomains">friendly domain</a> or
+sub-domain.
+</p>
+<a name="rfc.section.3.3.2.3"></a><h4><a name="procs.ownerP">3.3.2.3</a>&nbsp;ownerP</h4>
+</font><pre>
+ proc ownerP {local domain}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Returns "1" if the email-address refers to the user
+(as determined by looking at
+<a href="#options.myMailbox">myMailbox</a>,
+<a href="#options.pdaMailboxes">pdaMailboxes</a>, and
+<a href="#options.remoteMailboxes">remoteMailboxes</a>.
+</p>
+<a name="rfc.section.3.3.2.4"></a><h4><a name="procs.saveMessage">3.3.2.4</a>&nbsp;saveMessage</h4>
+</font><pre>
+ proc saveMessage {inF {outF ""}}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Saves a copy of the message contained in the file inF.
+If the destination file,
+outF,
+isn't specified,
+it defaults to the
+<a href="#options.defaultMaildrop">defaultMaildrop</a>.
+</p>
+<a name="rfc.section.3.3.2.5"></a><h4><a name="procs.findPhrase">3.3.2.5</a>&nbsp;findPhrase</h4>
+</font><pre>
+ proc findPhrase {subject}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Returns "1" if a previously-allocated passphrase is present in the
+subject.
+If so,
+the passphrase is forgotten.
+</p>
+<a name="rfc.section.3.3.2.6"></a><h4><a name="procs.makePhrase">3.3.2.6</a>&nbsp;makePhrase</h4>
+</font><pre>
+ proc makePhrase {}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Returns an at-most-once passphrase for use with a rejection notice.
+</p>
+<a name="rfc.section.3.3.2.7"></a><h4><a name="procs.pruneDir">3.3.2.7</a>&nbsp;pruneDir</h4>
+</font><pre>
+ proc pruneDir {dir type}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Removes old entries from one of the mailbot's
+<a href="#options.dataDirectory">databases</a>.
+The second parameter is one of "addr", "msgid", or "phrase".
+</p>
+<a name="rfc.section.3.3.2.8"></a><h4><a name="procs.tclLog">3.3.2.8</a>&nbsp;tclLog</h4>
+</font><pre>
+ proc tclLog {message}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Writes a message to the <a href="#options.logFile">logFile</a>.
+</p>
+<a name="rfc.references1"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<h3>References</h3>
+<table width="99%" border="0">
+</table>
+
+<a name="rfc.authors"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<h3>Author's Address</h3>
+<table width="99%" border="0" cellpadding="0" cellspacing="0">
+<tr><td class="author-text">&nbsp;</td>
+<td class="author-text">Marshall T. Rose</td></tr>
+<tr><td class="author-text">&nbsp;</td>
+<td class="author-text">Dover Beach Consulting, Inc.</td></tr>
+<tr><td class="author-text">&nbsp;</td>
+<td class="author-text">POB 255268</td></tr>
+<tr><td class="author-text">&nbsp;</td>
+<td class="author-text">Sacramento, CA 95865-5268</td></tr>
+<tr><td class="author-text">&nbsp;</td>
+<td class="author-text">US</td></tr>
+<tr><td class="author" align="right">Phone:&nbsp;</td>
+<td class="author-text">+1 916 483 8878</td></tr>
+<tr><td class="author" align="right">Fax:&nbsp;</td>
+<td class="author-text">+1 916 483 8848</td></tr>
+<tr><td class="author" align="right">EMail:&nbsp;</td>
+<td class="author-text"><a href="mailto:mrose@dbc.mtview.ca.us">mrose@dbc.mtview.ca.us</a></td></tr>
+</table>
+
+<a name="impersonal"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.A"></a><h3>Appendix A.&nbsp;Impersonal Mail</h3>
+
+<p>If <a href="#procs.impersonalMail">impersonalMail</a>
+returns a non-empty string
+then the message is processed differently than the algorithm given in
+<a href="#actions">Actions</a>.
+Specifically:
+
+<ol class="text">
+<li>If the message contains a previously-encountered "Message-ID",
+processing terminates.
+</li>
+<li>If the message's originator can not be determined,
+processing terminates.
+</li>
+<li>The value returned by
+<a href="#procs.impersonalMail">impersonalMail</a>
+is the folder's name and is broken into one or more components
+seperated by dots (".").
+If there aren't at least two components,
+or if any of the components are empty
+(e.g., the folder is named "sys..announce"),
+then the message is bounced.
+</li>
+<li>If <a href="#options.mappingFile">mappingFile</a> exists,
+that file is examined to see if an entry is present for the folder.
+If so,
+the message is processed according to the value present,
+one of:
+
+<blockquote class="text"><dl>
+<dt> "ignore":</dt>
+<dd>the message is silently ignored;
+</dd>
+<dt> "bounce":</dt>
+<dd>the message is noisily bounced; or,
+</dd>
+<dt> otherwise:</dt>
+<dd>the message is resent to the address.
+</dd>
+</dl></blockquote><p>
+Regardless,
+if an entry was present for the folder,
+then processing terminates.
+</li>
+<li>The message is <a href="#procs.saveMessage">saved</a>
+in a file whose name is constructed by replacing each dot (".") in the
+folder name with a directory seperator
+(e.g., if the folder is named "sys.announce",
+then the file is called "announce" underneath the directory "sys"
+underneath the directory identified by
+<a href="#options.foldersDirectory">foldersDirectory</a>.
+</li>
+<li>Finally,
+the file identified by <a href="#options.foldersFile">foldersFile</a>
+is updated as necessary.
+</li>
+</ol><p>
+</p>
+<a name="rfc.section.A.1"></a><h4><a name="impersonal.options">A.1</a>&nbsp;Configuration Options</h4>
+
+<p>If "impersonal" mail is received,
+then <a href="#options.foldersFile">foldersFile</a> and
+<a href="#options.foldersDirectory">foldersDirectory</a>
+must exist.
+</p>
+<a name="rfc.section.A.1.1"></a><h4><a name="options.foldersDirectory">A.1.1</a>&nbsp;foldersDirectory</h4>
+
+<p>The directory where the mailbot keeps private folders.
+</p>
+<a name="rfc.section.A.1.2"></a><h4><a name="options.foldersFile">A.1.2</a>&nbsp;foldersFile</h4>
+
+<p>This file contains one line for each private folder.
+</p>
+<a name="rfc.section.A.1.3"></a><h4><a name="options.announceMailboxes">A.1.3</a>&nbsp;announceMailboxes</h4>
+
+<p>The email-addresses where an announcement is sent when a new
+private folder is created.
+</p>
+<a name="rfc.section.A.1.4"></a><h4><a name="options.mappingFile">A.1.4</a>&nbsp;mappingFile</h4>
+
+<p>The file consulted by the mailbot to determine how to process
+"impersonal" messages.
+Each line of the file consists of a folder name and value,
+seperated by a colon (":").
+There are three reserved values: "bounce", "ignore", and "store".
+</p>
+<a name="rfc.section.A.2"></a><h4><a name="impersonal.procs">A.2</a>&nbsp;Configurable Procedures</h4>
+
+<p>All of these procedures are defined in personal.tcl.
+You may override any of them in configFile.
+</p>
+<a name="rfc.section.A.2.1"></a><h4><a name="procs.impersonalMail">A.2.1</a>&nbsp;impersonalMail</h4>
+</font><pre>
+ proc impersonalMail {}
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>If the message is deemed "impersonal",
+return the name of a corresponding private folder;
+otherwise,
+return the empty-string.
+</p>
+<p>Many mail systems have a mechanism of passing additional
+information when performing final delivery using a program.
+With modern versions of sendmail,
+for example,
+if mail is sent to a local user named "user+detail",
+then,
+in the absense of an alias for either "user+detail" or "user+*",
+then the message is delivered to "user".
+The trick is to get sendmail to pass the "detail" part to the mailbot.
+</p>
+<p>At present,
+sendmail passes this information only if procmail is your local
+mailer.
+Here's how I do it:
+</p></font><pre>
+ *** _alias.c Tue Dec 29 10:42:25 1998
+ --- alias.c Sat Sep 18 21:51:35 1999
+ ***************
+ *** 813,818 ****
+ --- 813,821 ----
+ define('z', user->q_home, e);
+ define('u', user->q_user, e);
+ define('h', user->q_host, e);
+ +
+ + setuserenv("SUFFIX", user->q_host);
+ +
+ if (ForwardPath == NULL)
+ ForwardPath = newstr("\201z/.forward");
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>This makes available an environment variable called
+"SUFFIX" which has the "details" part.
+The drawback in this approach is that this information is lost if the
+message is re-queued for delivery
+(what's really needed is an addition to the .forward syntax to allow
+macros such as $h to be passed).
+</p>
+<p>The corresponding impersonalMail procedure is defined as:
+</p></font><pre>
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<a name="rfc.section.A.2.2"></a><h4><a name="procs.processFolder">A.2.2</a>&nbsp;processFolder</h4>
+</font><pre>
+ proc processFolder {folderName mimeT} { return $string }
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>If an entry for the folder exists in the
+<a href="#options.mappingFile">mappingFile</a>,
+and if the value for that entry is "process",
+then this procedure is invoked to return a string indicating what
+action to take
+(cf., <a href="#impersonal">Impersonal Mail</a>).
+</p>
+<a name="anchor8"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.B"></a><h3>Appendix B.&nbsp;An Example configFile</h3>
+
+<p>Here is the ".forward" file for the user "hewes":
+</p></font><pre>
+ "|/usr/pkg/lib/mbot-1.1/personal.tcl
+ -config .personal/config.tcl -user hewes"
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>(Of course, it's all on one line.)
+</p>
+<p>Here is the user's ".personal/config.tcl" file:
+</p></font><pre>
+ array set options [list \
+ dataDirectory .personal \
+ defaultMaildrop /var/mail/hewes \
+ auditInFile [file join .personal INCOMING] \
+ auditOutFile [file join .personal OUTGOING] \
+ friendlyDomains [list tcp.int example.com] \
+ logFile [file join .personal personal.log] \
+ myMailbox "Arlington Hewes &lt;hewes@example.com>" \
+ pdaMailboxes hewes.pager@example.com \
+ noticeFile [file join .personal notice.txt] \
+ foldersDirectory [file join .personal folders] \
+ foldersFile [file join .personal .mailboxlist] \
+ announceMailboxes hewes+sys.announce@example.com \
+ mappingFile [file join .personal mapping] \
+ friendlyFire 1 \
+ dropNames [list *.bat *.exe *.src *.pif *.wav *.vbs] \
+ ]
+
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+</pre><font face="verdana, helvetica, arial, sans-serif" size="2">
+
+<p>Note that because
+<a href="#options.remoteMailboxes">remoteMailboxes</a> isn't
+defined,
+personal messages are ultimately stored in the user's
+<a href="#options.defaultMaildrop">defaultMaildrop</a>.
+</p>
+<a name="anchor9"><br><hr size="1" shade="0"></a>
+<table border="0" cellpadding="0" cellspacing="2" width="30" height="15" align="right"><tr><td bgcolor="#990000" align="center" width="30" height="15"><a href="#toc" CLASS="link2"><font face="monaco, MS Sans Serif" color="#ffffff" size="1"><b>&nbsp;TOC&nbsp;</b></font></a><br></td></tr></table>
+<a name="rfc.section.C"></a><h3>Appendix C.&nbsp;Acknowledgements</h3>
+
+<p>The original version of this mailbot was written by the author in 1994,
+implemented using the safe-tcl package
+(Borenstein and Rose, circa 1993).
+</p></font></body></html>
diff --git a/tcllib/examples/mime/mbot/README.txt b/tcllib/examples/mime/mbot/README.txt
new file mode 100644
index 0000000..509d502
--- /dev/null
+++ b/tcllib/examples/mime/mbot/README.txt
@@ -0,0 +1,1008 @@
+
+
+The README file M. Rose
+ Dover Beach Consulting, Inc.
+ February 2002
+
+
+ The personal.tcl Mailbot
+
+
+Abstract
+
+ The personal.tcl mailbot implements a highly-specialized filter for
+ personal messages. It MUST not be used by people who receive mailing
+ list traffic in their personal mailboxes.
+
+Table of Contents
+
+ 1. SYNOPSIS . . . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 1.1 Requirements . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 1.2 Copyrights . . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 2. PHILOSOPHY . . . . . . . . . . . . . . . . . . . . . . . . . 3
+ 2.1 Guest Lists . . . . . . . . . . . . . . . . . . . . . . . . 4
+ 3. BEHAVIOR . . . . . . . . . . . . . . . . . . . . . . . . . . 5
+ 3.1 Arguments . . . . . . . . . . . . . . . . . . . . . . . . . 5
+ 3.2 Actions . . . . . . . . . . . . . . . . . . . . . . . . . . 6
+ 3.3 The Configuration File . . . . . . . . . . . . . . . . . . . 7
+ 3.3.1 Configuration Options . . . . . . . . . . . . . . . . . . . 7
+ 3.3.2 Configurable Procedures . . . . . . . . . . . . . . . . . . 10
+ References . . . . . . . . . . . . . . . . . . . . . . . . . 12
+ Author's Address . . . . . . . . . . . . . . . . . . . . . . 12
+ A. Impersonal Mail . . . . . . . . . . . . . . . . . . . . . . 13
+ A.1 Configuration Options . . . . . . . . . . . . . . . . . . . 14
+ A.1.1 foldersDirectory . . . . . . . . . . . . . . . . . . . . . . 14
+ A.1.2 foldersFile . . . . . . . . . . . . . . . . . . . . . . . . 14
+ A.1.3 announceMailboxes . . . . . . . . . . . . . . . . . . . . . 14
+ A.1.4 mappingFile . . . . . . . . . . . . . . . . . . . . . . . . 14
+ A.2 Configurable Procedures . . . . . . . . . . . . . . . . . . 15
+ A.2.1 impersonalMail . . . . . . . . . . . . . . . . . . . . . . . 15
+ A.2.2 processFolder . . . . . . . . . . . . . . . . . . . . . . . 16
+ B. An Example configFile . . . . . . . . . . . . . . . . . . . 17
+ C. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . 18
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 1]
+
+README The personal.tcl Mailbot February 2002
+
+
+1. SYNOPSIS
+
+ Create a configuration file (Section 3.3) and add this line to your
+ ".forward" file:
+
+ "| LIB/mbot-1.1/personal.tcl -config FILE -user USER"
+
+ where "LIB" is where the Tcl library lives, "FILE" is the name of
+ your configuration file, and "USER" is your username.
+
+1.1 Requirements
+
+ This package requires:
+
+ o Tcl version 8.3 [1] or later
+
+ o tcl lib [2]
+
+ o TclX version 8.0 [3] or later
+
+
+1.2 Copyrights
+
+ (c) 1999-2002 Marshall T. Rose
+
+ Hold harmless the author, and any lawful use is allowed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 2]
+
+README The personal.tcl Mailbot February 2002
+
+
+2. PHILOSOPHY
+
+ The mailbot's philosophy is simple:
+
+ o The mailbot receives all of your incoming personal mail.
+
+ o You ALWAYS copy yourself on every message you send, so that the
+ mailbot receives all of your outgoing personal mail.
+
+ o The mailbot performs six tasks, all optional:
+
+ * makes audit copies of your incoming and outgoing mail;
+
+ * performs duplicate supression;
+
+ * performs originator supression by rejecting messages from
+ people who aren't your friends or on a guest list;
+
+ * performs content supression by rejecting messages that contain
+ attachments with extensions on your prohibited list;
+
+ * sends a textual synopsis to your PDA; and,
+
+ * sends a copy to your remote mailbox.
+
+ Do NOT use the personal.tcl mailbot if you receive mailing list
+ traffic in your personal mailbox. When sending mail to a mailing
+ list, either:
+
+ o use a "From" address that the personal.tcl mailbot will process as
+ "impersonal" mail, (e.g., "hewes+ietf.general@example.com"); or,
+
+ o set the "Reply-To" for the message to the mailing list.
+
+ Consult Appendix A for information on how "impersonal" mail is
+ identified and processed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 3]
+
+README The personal.tcl Mailbot February 2002
+
+
+2.1 Guest Lists
+
+ Guest lists are an effective mechanism for cutting back on excessive
+ mail.
+
+ o when the mailbot receives a message from you, it adds any
+ recipients it finds to a permanent-guest list;
+
+ o when the mailbot receives a message from someone on a guest list,
+ it adds any recipients it finds to a temporary-guest list; but,
+
+ o when the mailbot receives a message from someone not on any guest
+ list, they get a rejection notice.
+
+ Note that in order to promote someone to the permanent-guest list,
+ you must send them a message (with a copy to yourself). In most
+ cases, simply replying to the original message accomplishes this. Of
+ course, if you don't want to promote someone to the permanent-guest
+ list, simply remove that address (or your address) from the list of
+ recipients in your reply.
+
+ Here are the fine points:
+
+ o rejection notices contain a passphrase that may be used at most
+ once to bypass the guest list mechanism (notices also contain the
+ original message to minimize type-in by the uninvited);
+
+ o a flip-flop is used to avoid mail loops; and,
+
+ o messages originated by an administrative address (e.g.,
+ "Postmaster") bypass the guest list mechanism (unless the message
+ refers to a previously-rejected message, in which case it is
+ supressed).
+
+ The rejection notice should be written carefully to minimize an
+ extreme negative reaction on the part of the uninvited. Of course,
+ by allowing a passphrase, this provides something of a CQ test for
+ the uninvited -- if someone can't pass the test...
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 4]
+
+README The personal.tcl Mailbot February 2002
+
+
+3. BEHAVIOR
+
+3.1 Arguments
+
+ The mailbot supports the following command line arguments:
+
+ -config configFile: specifies the name of the configuration file
+ to use;
+
+ -debug boolean: enables debug output;
+
+ -file messageFile: specifies the name of the file containing the
+ message;
+
+ -originator orginatorAddress: specifies the email-address of the
+ originator of the message; and,
+
+ -user userName: specifies the user-identity of the recipient.
+
+ Note that if "-user" is given, then the working directory is set to
+ userName's home directory before configFile is sourced, and the umask
+ is set defensively.
+
+ The default values are:
+
+ personal.tcl -config .personal-config.tcl \
+ -debug 0 \
+ -file - \
+ -originator "derived from message"
+
+ Given the default values, only "-user" need be specified. The reason
+ is that if a message is being delivered to multiple local recipients,
+ and if any of the ".forward" files are identical in content, then
+ sendmail may not deliver the message to all of the local recipients.
+
+ A few other (sendmail related) tips:
+
+ o If sendmail is configured with smrsh, you'll need to symlink
+ personal.tcl into the /usr/libexec/sm.bin/ directory.
+
+ o Make sure that tclsh8.0 is in the path specified on the third-line
+ of personal.tcl.
+
+ o You should chmod your ".forward" file to 0600.
+
+
+
+
+
+
+
+Rose [Page 5]
+
+README The personal.tcl Mailbot February 2002
+
+
+3.2 Actions
+
+ The mailbot begins by parsing its arguments, sourcing configFile, and
+ then examining the incoming message:
+
+ 1. If auditInFile (Section 3.3.1.3) is set, a copy of the message is
+ saved (Section 3.3.2.4) there.
+
+ 2. If the message contains a previously-encountered "Message-ID",
+ processing terminates.
+
+ 3. If the message's originator can not be determined, a copy of the
+ message is saved (Section 3.3.2.4) in the defaultMaildrop
+ (Section 3.3.1.2) and processing terminates.
+
+ 4. The originator's email-address is examined:
+
+ 1. If the originator appears to be an automated administrative
+ process (Section 3.3.2.1), and if a previously rejected
+ email-address is found in the message, processing terminates.
+
+ 2. Otherwise, if the originator isn't the user (Section
+ 3.3.2.3), or a friend (Section 3.3.2.2), or a permanent-
+ access guest, or a temporary-access guest, and if noticeFile
+ (Section 3.3.1.10) is set, then the message is rejected.
+
+ 3. Otherwise, each recipient email-address in the message's
+ header is added to a guest list. (If the originator is the
+ user (Section 3.3.2.3), the permanent-guest list is used
+ instead of the temporary-guest list.)
+
+ 5. If the originator is the the user (Section 3.3.2.3), then:
+
+ 1. If auditOutFile (Section 3.3.1.4) is set, saved (Section
+ 3.3.2.4) there.
+
+ 2. Regardless, processing terminates.
+
+ 6. If pdaMailboxes (Section 3.3.1.11) is set, and if any plaintext
+ is contained in the message, then the plaintext is sent to those
+ email-addresses.
+
+ 7. If remoteMailboxes (Section 3.3.1.12) is set, and if the message
+ is successful resent to those email-addresses, then processing
+ terminates.
+
+ 8. A copy of the message is saved (Section 3.3.2.4) in the
+ defaultMaildrop (Section 3.3.1.2) and processing terminates.
+
+
+
+Rose [Page 6]
+
+README The personal.tcl Mailbot February 2002
+
+
+3.3 The Configuration File
+
+ There are two kinds of information that may be defined in configFile:
+ configuration options (Section 3.3.1) and configurable procedures
+ (Section 3.3.2).
+
+ Here's a simple example of a configFile for a user named "example":
+
+ set options(dataDirectory) .personal
+ set options(defaultMaildrop) /var/mail/example
+ set options(logFile) [file join .personal personal.log]
+ set options(noticeFile) [file join .personal notice.txt]
+
+
+3.3.1 Configuration Options
+
+ configFile must define dataDirectory (Section 3.3.1.1) and
+ defaultMaildrop (Section 3.3.1.2). All other configuration options
+ are optional.
+
+3.3.1.1 dataDirectory
+
+ The directory where the mailbot keeps its databases. The
+ subdirectories are:
+
+ badaddrs: the directory of rejected email-addresses
+
+ inaddrs: the directory of originator email-addresses
+
+ msgids: the directory of Message-IDs
+
+ outaddrs: the permanent-guest list
+
+ phrases: the directory of at-most-once passphrases
+
+ tmpaddrs: the temporary-guest list
+
+ If you want to remove someone from a guest list, simply go to that
+ directory and delete the corresponding file.
+
+3.3.1.2 defaultMaildrop
+
+ The filename where messages are saved (Section 3.3.2.4) for later
+ viewing by your user agent.
+
+3.3.1.3 auditInFile
+
+ The filename where messages are saved (Section 3.3.2.4) for audit
+
+
+
+Rose [Page 7]
+
+README The personal.tcl Mailbot February 2002
+
+
+ purposes.
+
+3.3.1.4 auditOutFile
+
+ The filename where your outgoing messages are saved (Section 3.3.2.4)
+ for audit purposes.
+
+3.3.1.5 dropNames
+
+ A list of filename extensions for attachments that automatically
+ cause the message to be rejected.
+
+3.3.1.6 friendlyDomains
+
+ A list used by friendP (Section 3.3.2.2) giving the domain names
+ where your friends live.
+
+3.3.1.7 friendlyfire
+
+ If present and true, then someone sending a message both to you and
+ someone you've previously sent mail to, is considered a friend.
+
+3.3.1.8 logFile
+
+ The filename where the mailbot logs (Section 3.3.2.8) its actions.
+
+3.3.1.9 myMailbox
+
+ Your preferred email-address with commentary text, e.g.,
+
+ Arlington Hewes <hewes@example.com>
+
+
+3.3.1.10 noticeFile
+
+ The filename containing the textual notice sent when a message is
+ rejected. Note that all occurrances of "%passPhrase%" within this
+ file are replaced with an at-most-once passphrase allowing the
+ originator to bypass the mailbot's filtering. Similarly, any
+ occurrences of "%subject%" are replaced by the "Subject" of the
+ incoming message.
+
+3.3.1.11 pdaMailboxes
+
+ The email-addresses where a textual synopsis of the incoming message
+ is sent.
+
+
+
+
+
+Rose [Page 8]
+
+README The personal.tcl Mailbot February 2002
+
+
+3.3.1.12 remoteMailboxes
+
+ The email-addresses where a copy of the incoming message is resent.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 9]
+
+README The personal.tcl Mailbot February 2002
+
+
+3.3.2 Configurable Procedures
+
+ All of these procedures are defined in personal.tcl. You may
+ override any of them in configFile.
+
+3.3.2.1 adminP
+
+ proc adminP {local domain}
+
+ Returns "1" if the email-address is an automated administrative
+ process.
+
+3.3.2.2 friendP
+
+ proc friendP {local domain}
+
+ Returns "1" if the email-address is from a friendly domain (Section
+ 3.3.1.6) or sub-domain.
+
+3.3.2.3 ownerP
+
+ proc ownerP {local domain}
+
+ Returns "1" if the email-address refers to the user (as determined by
+ looking at myMailbox (Section 3.3.1.9), pdaMailboxes (Section
+ 3.3.1.11), and remoteMailboxes (Section 3.3.1.12).
+
+3.3.2.4 saveMessage
+
+ proc saveMessage {inF {outF ""}}
+
+ Saves a copy of the message contained in the file inF. If the
+ destination file, outF, isn't specified, it defaults to the
+ defaultMaildrop (Section 3.3.1.2).
+
+3.3.2.5 findPhrase
+
+ proc findPhrase {subject}
+
+ Returns "1" if a previously-allocated passphrase is present in the
+ subject. If so, the passphrase is forgotten.
+
+3.3.2.6 makePhrase
+
+ proc makePhrase {}
+
+ Returns an at-most-once passphrase for use with a rejection notice.
+
+
+
+
+Rose [Page 10]
+
+README The personal.tcl Mailbot February 2002
+
+
+3.3.2.7 pruneDir
+
+ proc pruneDir {dir type}
+
+ Removes old entries from one of the mailbot's databases (Section
+ 3.3.1.1). The second parameter is one of "addr", "msgid", or
+ "phrase".
+
+3.3.2.8 tclLog
+
+ proc tclLog {message}
+
+ Writes a message to the logFile (Section 3.3.1.8).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 11]
+
+README The personal.tcl Mailbot February 2002
+
+
+References
+
+ [1] <http://core.tcl.tk/tcl/>
+
+ [2] <http://core.tcl.tk/tcllib/>
+
+ [3] <http://sourceforge.net/projects/tclx/>
+
+
+Author's Address
+
+ Marshall T. Rose
+ Dover Beach Consulting, Inc.
+ POB 255268
+ Sacramento, CA 95865-5268
+ US
+
+ Phone: +1 916 483 8878
+ Fax: +1 916 483 8848
+ EMail: mrose@dbc.mtview.ca.us
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 12]
+
+README The personal.tcl Mailbot February 2002
+
+
+Appendix A. Impersonal Mail
+
+ If impersonalMail (Appendix A.2.1) returns a non-empty string then
+ the message is processed differently than the algorithm given in
+ Section 3.2. Specifically:
+
+ 1. If the message contains a previously-encountered "Message-ID",
+ processing terminates.
+
+ 2. If the message's originator can not be determined, processing
+ terminates.
+
+ 3. The value returned by impersonalMail (Appendix A.2.1) is the
+ folder's name and is broken into one or more components seperated
+ by dots ("."). If there aren't at least two components, or if
+ any of the components are empty (e.g., the folder is named
+ "sys..announce"), then the message is bounced.
+
+ 4. If mappingFile (Appendix A.1.4) exists, that file is examined to
+ see if an entry is present for the folder. If so, the message is
+ processed according to the value present, one of:
+
+ "ignore": the message is silently ignored;
+
+ "bounce": the message is noisily bounced; or,
+
+ otherwise: the message is resent to the address.
+
+ Regardless, if an entry was present for the folder, then
+ processing terminates.
+
+ 5. The message is saved (Section 3.3.2.4) in a file whose name is
+ constructed by replacing each dot (".") in the folder name with a
+ directory seperator (e.g., if the folder is named "sys.announce",
+ then the file is called "announce" underneath the directory "sys"
+ underneath the directory identified by foldersDirectory (Appendix
+ A.1.1).
+
+ 6. Finally, the file identified by foldersFile (Appendix A.1.2) is
+ updated as necessary.
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 13]
+
+README The personal.tcl Mailbot February 2002
+
+
+A.1 Configuration Options
+
+ If "impersonal" mail is received, then foldersFile (Appendix A.1.2)
+ and foldersDirectory (Appendix A.1.1) must exist.
+
+A.1.1 foldersDirectory
+
+ The directory where the mailbot keeps private folders.
+
+A.1.2 foldersFile
+
+ This file contains one line for each private folder.
+
+A.1.3 announceMailboxes
+
+ The email-addresses where an announcement is sent when a new private
+ folder is created.
+
+A.1.4 mappingFile
+
+ The file consulted by the mailbot to determine how to process
+ "impersonal" messages. Each line of the file consists of a folder
+ name and value, seperated by a colon (":"). There are three reserved
+ values: "bounce", "ignore", and "store".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 14]
+
+README The personal.tcl Mailbot February 2002
+
+
+A.2 Configurable Procedures
+
+ All of these procedures are defined in personal.tcl. You may
+ override any of them in configFile.
+
+A.2.1 impersonalMail
+
+ proc impersonalMail {}
+
+ If the message is deemed "impersonal", return the name of a
+ corresponding private folder; otherwise, return the empty-string.
+
+ Many mail systems have a mechanism of passing additional information
+ when performing final delivery using a program. With modern versions
+ of sendmail, for example, if mail is sent to a local user named
+ "user+detail", then, in the absense of an alias for either
+ "user+detail" or "user+*", then the message is delivered to "user".
+ The trick is to get sendmail to pass the "detail" part to the
+ mailbot.
+
+ At present, sendmail passes this information only if procmail is your
+ local mailer. Here's how I do it:
+
+ *** _alias.c Tue Dec 29 10:42:25 1998
+ --- alias.c Sat Sep 18 21:51:35 1999
+ ***************
+ *** 813,818 ****
+ --- 813,821 ----
+ define('z', user->q_home, e);
+ define('u', user->q_user, e);
+ define('h', user->q_host, e);
+ +
+ + setuserenv("SUFFIX", user->q_host);
+ +
+ if (ForwardPath == NULL)
+ ForwardPath = newstr("\201z/.forward");
+
+ This makes available an environment variable called "SUFFIX" which
+ has the "details" part. The drawback in this approach is that this
+ information is lost if the message is re-queued for delivery (what's
+ really needed is an addition to the .forward syntax to allow macros
+ such as $h to be passed).
+
+
+
+
+
+
+
+
+
+Rose [Page 15]
+
+README The personal.tcl Mailbot February 2002
+
+
+ The corresponding impersonalMail procedure is defined as:
+
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+
+
+A.2.2 processFolder
+
+ proc processFolder {folderName mimeT} { return $string }
+
+ If an entry for the folder exists in the mappingFile (Appendix
+ A.1.4), and if the value for that entry is "process", then this
+ procedure is invoked to return a string indicating what action to
+ take (cf., Appendix A).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 16]
+
+README The personal.tcl Mailbot February 2002
+
+
+Appendix B. An Example configFile
+
+ Here is the ".forward" file for the user "hewes":
+
+ "|/usr/pkg/lib/mbot-1.1/personal.tcl
+ -config .personal/config.tcl -user hewes"
+
+ (Of course, it's all on one line.)
+
+ Here is the user's ".personal/config.tcl" file:
+
+ array set options [list \
+ dataDirectory .personal \
+ defaultMaildrop /var/mail/hewes \
+ auditInFile [file join .personal INCOMING] \
+ auditOutFile [file join .personal OUTGOING] \
+ friendlyDomains [list tcp.int example.com] \
+ logFile [file join .personal personal.log] \
+ myMailbox "Arlington Hewes <hewes@example.com>" \
+ pdaMailboxes hewes.pager@example.com \
+ noticeFile [file join .personal notice.txt] \
+ foldersDirectory [file join .personal folders] \
+ foldersFile [file join .personal .mailboxlist] \
+ announceMailboxes hewes+sys.announce@example.com \
+ mappingFile [file join .personal mapping] \
+ friendlyFire 1 \
+ dropNames [list *.bat *.exe *.src *.pif *.wav *.vbs] \
+ ]
+
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+
+ Note that because remoteMailboxes (Section 3.3.1.12) isn't defined,
+ personal messages are ultimately stored in the user's defaultMaildrop
+ (Section 3.3.1.2).
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 17]
+
+README The personal.tcl Mailbot February 2002
+
+
+Appendix C. Acknowledgements
+
+ The original version of this mailbot was written by the author in
+ 1994, implemented using the safe-tcl package (Borenstein and Rose,
+ circa 1993).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Rose [Page 18]
+
diff --git a/tcllib/examples/mime/mbot/README.xml b/tcllib/examples/mime/mbot/README.xml
new file mode 100644
index 0000000..8d23d9b
--- /dev/null
+++ b/tcllib/examples/mime/mbot/README.xml
@@ -0,0 +1,720 @@
+<?xml version="1.0"?>
+<!DOCTYPE rfc SYSTEM "rfc2629.dtd">
+
+<?rfc compact="no"?>
+<?rfc toc="yes"?>
+<?rfc private="The README file"?>
+<?rfc header="README"?>
+
+<rfc>
+<front>
+<title>The personal.tcl Mailbot</title>
+
+<author initials="M.T." surname="Rose" fullname="Marshall T. Rose">
+<organization>Dover Beach Consulting, Inc.</organization>
+<address>
+<postal>
+<street>POB 255268</street>
+<city>Sacramento</city> <region>CA</region> <code>95865-5268</code>
+<country>US</country>
+</postal>
+<phone>+1 916 483 8878</phone>
+<facsimile>+1 916 483 8848</facsimile>
+<email>mrose@dbc.mtview.ca.us</email>
+</address>
+</author>
+
+<date month="February" year="2002" />
+
+<abstract><t>The personal.tcl mailbot implements a highly-specialized
+filter for personal messages.
+It MUST not be used by people who receive mailing list traffic in
+their personal mailboxes.</t></abstract>
+</front>
+
+<middle>
+<section title="SYNOPSIS">
+<figure>
+<preamble>Create a <xref target="configFile">configuration file</xref>
+and add this line to your ".forward" file:</preamble>
+<artwork><![CDATA[
+ "| LIB/mbot-1.1/personal.tcl -config FILE -user USER"
+]]></artwork>
+<postamble>where "LIB" is where the Tcl library lives,
+"FILE" is the name of your configuration file,
+and "USER" is your username.</postamble>
+</figure>
+
+<section title="Requirements">
+<t>This package requires:
+<list style="symbols">
+<t><eref target="http://core.tcl.tk/tcl/">Tcl version 8.3</eref>
+or later</t>
+
+<t><eref target="http://core.tcl.tk/tcllib/">tcl lib</eref></t>
+
+<t><eref target="http://sourceforge.net/projects/tclx/">TclX version 8.0</eref>
+or later</t>
+</list></t>
+</section>
+
+<section title="Copyrights">
+<t>(c) 1999-2002 Marshall T. Rose</t>
+
+<t>Hold harmless the author, and any lawful use is allowed.</t>
+</section>
+</section>
+
+<section title="PHILOSOPHY">
+<t>The mailbot's philosophy is simple:
+<list style="symbols">
+<t>The mailbot receives all of your incoming personal mail.</t>
+
+<t>You ALWAYS copy yourself on every message you send,
+so that the mailbot receives all of your outgoing personal mail.</t>
+
+<t>The mailbot performs six tasks, all optional:
+<list>
+<t>makes audit copies of your incoming and outgoing mail;</t>
+
+<t>performs duplicate supression;</t>
+
+<t>performs originator supression by rejecting messages from people
+who aren't your friends or on a guest list;</t>
+
+<t>performs content supression by rejecting messages that contain
+attachments with extensions on your prohibited list;</t>
+
+<t>sends a textual synopsis to your PDA; and,</t>
+
+<t>sends a copy to your remote mailbox.</t>
+</list></t>
+</list></t>
+
+<t>Do NOT use the personal.tcl mailbot if you receive mailing list
+traffic in your personal mailbox.
+When sending mail to a mailing list,
+either:
+<list style="symbols">
+<t>use a "From" address that the personal.tcl mailbot will process as
+"impersonal" mail,
+(e.g., "hewes+ietf.general@example.com"); or,</t>
+
+<t>set the "Reply-To" for the message to the mailing list.</t>
+</list>
+Consult <xref target="impersonal" /> for information on how
+"impersonal" mail is identified and processed.</t>
+
+<vspace blankLines="10000" />
+
+<section title="Guest Lists">
+<t>Guest lists are an effective mechanism for cutting back on
+excessive mail.
+<list style="symbols">
+<t>when the mailbot receives a message from you,
+it adds any recipients it finds to a permanent-guest list;</t>
+
+<t>when the mailbot receives a message from someone on a guest list,
+it adds any recipients it finds to a temporary-guest list; but,</t>
+
+<t>when the mailbot receives a message from someone not on any guest
+list,
+they get a rejection notice.</t>
+</list>
+Note that in order to promote someone to the permanent-guest list,
+you must send them a message (with a copy to yourself).
+In most cases,
+simply replying to the original message accomplishes this.
+Of course,
+if you don't want to promote someone to the permanent-guest list,
+simply remove that address (or your address) from the list of
+recipients in your reply.</t>
+
+<t>Here are the fine points:
+<list style="symbols">
+<t>rejection notices contain a passphrase that may be used at most
+once to bypass the guest list mechanism
+(notices also contain the original message to minimize type-in
+by the uninvited);</t>
+
+<t>a flip-flop is used to avoid mail loops; and,</t>
+
+<t>messages originated by an administrative address (e.g.,
+"Postmaster") bypass the guest list mechanism
+(unless the message refers to a previously-rejected message,
+in which case it is supressed).</t>
+</list></t>
+
+<t>The rejection notice should be written carefully to minimize an
+extreme negative reaction on the part of the uninvited.
+Of course,
+by allowing a passphrase,
+this provides something of a CQ test for the uninvited --
+if someone can't pass the test...</t>
+</section>
+</section>
+
+<section title="BEHAVIOR">
+<section title="Arguments">
+<t>The mailbot supports the following command line arguments:
+<list style="hanging">
+<t hangText=" -config configFile:">
+specifies the name of the configuration file to use;</t>
+
+<t hangText=" -debug boolean:">
+enables debug output;</t>
+
+<t hangText=" -file messageFile:">
+specifies the name of the file containing the message;</t>
+
+<t hangText=" -originator orginatorAddress:">
+specifies the email-address of the originator of the message; and,</t>
+
+<t hangText=" -user userName:">
+specifies the user-identity of the recipient.</t>
+</list>
+Note that if "-user" is given,
+then the working directory is set to userName's home directory before
+configFile is sourced,
+and the umask is set defensively.</t>
+
+<figure>
+<preamble>The default values are:</preamble>
+<artwork><![CDATA[
+ personal.tcl -config .personal-config.tcl \
+ -debug 0 \
+ -file - \
+ -originator "derived from message"
+]]></artwork>
+<postamble>Given the default values,
+only "-user" need be specified.
+The reason is that if a message is being delivered to multiple local
+recipients,
+and if any of the ".forward" files are identical in content,
+then sendmail may not deliver the message to all of the local
+recipients.</postamble>
+</figure>
+
+<t>A few other (sendmail related) tips:
+<list style="symbols">
+<t>If sendmail is configured with smrsh,
+you'll need to symlink personal.tcl into the
+/usr/libexec/sm.bin/ directory.</t>
+
+<t>Make sure that tclsh8.0 is in the path specified on the third-line
+of personal.tcl.</t>
+
+<t>You should chmod your ".forward" file to 0600.</t>
+</list></t>
+</section>
+
+<vspace blankLines="10000" />
+
+<section anchor="actions" title="Actions">
+<t>The mailbot begins by parsing its arguments,
+sourcing configFile,
+and then examining the incoming message:
+<list style="numbers">
+<t>If <xref target="options.auditInFile">auditInFile</xref> is set,
+a copy of the message is
+<xref target="procs.saveMessage">saved</xref> there.</t>
+
+<t>If the message contains a previously-encountered "Message-ID",
+processing terminates.</t>
+
+<t>If the message's originator can not be determined,
+a copy of the message is
+<xref target="procs.saveMessage">saved</xref> in the
+<xref target="options.defaultMaildrop">defaultMaildrop</xref> and
+processing terminates.</t>
+
+<t>The originator's email-address is examined:
+<list>
+<t>If the originator appears to be an
+<xref target="procs.adminP">automated administrative process</xref>,
+and if a previously rejected email-address is found in the message,
+processing terminates.</t>
+
+<t>Otherwise,
+if the originator isn't <xref target="procs.ownerP">the user</xref>,
+or <xref target="procs.friendP">a friend</xref>,
+or a permanent-access guest,
+or a temporary-access guest,
+and if <xref target="options.noticeFile">noticeFile</xref> is set,
+then the message is rejected.</t>
+
+<t>Otherwise,
+each recipient email-address in the message's header is added to a guest
+list.
+(If the originator is <xref target="procs.ownerP">the user</xref>,
+the permanent-guest list is used instead of the temporary-guest
+list.)</t>
+</list></t>
+
+<t>If the originator is the <xref target="procs.ownerP">the user</xref>,
+then:
+<list>
+<t>If <xref target="options.auditOutFile">auditOutFile</xref> is set,
+<xref target="procs.saveMessage">saved</xref> there.</t>
+
+<t>Regardless, processing terminates.</t>
+</list></t>
+
+<t>If <xref target="options.pdaMailboxes">pdaMailboxes</xref> is set,
+and if any plaintext is contained in the message,
+then the plaintext is sent to those email-addresses.</t>
+
+<t>If <xref target="options.remoteMailboxes">remoteMailboxes</xref> is set,
+and if the message is successful resent to those email-addresses,
+then processing terminates.</t>
+
+<t>A copy of the message is
+<xref target="procs.saveMessage">saved</xref> in the
+<xref target="options.defaultMaildrop">defaultMaildrop</xref> and
+processing terminates.</t>
+</list></t>
+</section>
+
+<section anchor="configFile" title="The Configuration File">
+<t>There are two kinds of information that may be defined in configFile:
+<xref target="options">configuration options</xref> and
+<xref target="procs">configurable procedures</xref>.</t>
+
+<figure>
+<preamble>Here's a simple example of a configFile for a user named
+"example":</preamble>
+<artwork><![CDATA[
+ set options(dataDirectory) .personal
+ set options(defaultMaildrop) /var/mail/example
+ set options(logFile) [file join .personal personal.log]
+ set options(noticeFile) [file join .personal notice.txt]
+]]></artwork>
+</figure>
+
+<section anchor="options" title="Configuration Options">
+<t>configFile must define
+<xref target="options.dataDirectory">dataDirectory</xref>
+and
+<xref target="options.defaultMaildrop">defaultMaildrop</xref>.
+All other configuration options are optional.</t>
+
+<section anchor="options.dataDirectory" title="dataDirectory">
+<t>The directory where the mailbot keeps its databases.
+The subdirectories are:
+<list style="hanging">
+<t hangText=" badaddrs:">the directory of rejected email-addresses</t>
+
+<t hangText=" inaddrs:">the directory of originator email-addresses</t>
+
+<t hangText=" msgids:">the directory of Message-IDs</t>
+
+<t hangText=" outaddrs:">the permanent-guest list</t>
+
+<t hangText=" phrases:">the directory of at-most-once passphrases</t>
+
+<t hangText=" tmpaddrs:">the temporary-guest list</t>
+</list>
+If you want to remove someone from a guest list,
+simply go to that directory and delete the corresponding file.</t>
+</section>
+
+<section anchor="options.defaultMaildrop" title="defaultMaildrop">
+<t>The filename where messages are
+<xref target="procs.saveMessage">saved</xref> for later viewing by
+your user agent.</t>
+</section>
+
+<section anchor="options.auditInFile" title="auditInFile">
+<t>The filename where messages are
+<xref target="procs.saveMessage">saved</xref> for audit purposes.</t>
+</section>
+
+<section anchor="options.auditOutFile" title="auditOutFile">
+<t>The filename where your outgoing messages are
+<xref target="procs.saveMessage">saved</xref> for audit purposes.</t>
+</section>
+
+<section anchor="options.dropNames" title="dropNames">
+<t>A list of filename extensions for attachments that automatically
+cause the message to be rejected.</t>
+</section>
+
+<section anchor="options.friendlyDomains" title="friendlyDomains">
+<t>A list used by <xref target="procs.friendP">friendP</xref> giving
+the domain names where your friends live.</t>
+</section>
+
+<section anchor="options.friendlyfire" title="friendlyfire">
+<t>If present and true,
+then someone sending a message both to you and someone you've
+previously sent mail to,
+is considered a friend.</t>
+</section>
+
+<section anchor="options.logFile" title="logFile">
+<t>The filename where the mailbot
+<xref target="procs.tclLog">logs</xref> its actions.</t>
+</section>
+
+<section anchor="options.myMailbox" title="myMailbox">
+<figure>
+<preamble>Your preferred email-address with commentary text, e.g.,</preamble>
+<artwork><![CDATA[
+ Arlington Hewes <hewes@example.com>
+]]></artwork>
+</figure>
+</section>
+
+<section anchor="options.noticeFile" title="noticeFile">
+<t>The filename containing the textual notice sent when a message is
+rejected.
+Note that all occurrances of "%passPhrase%" within this file are
+replaced with an at-most-once passphrase allowing the originator to
+bypass the mailbot's filtering.
+Similarly,
+any occurrences of "%subject%" are replaced by the "Subject" of the
+incoming message.</t>
+</section>
+
+<section anchor="options.pdaMailboxes" title="pdaMailboxes">
+<t>The email-addresses where a textual synopsis of the incoming message is
+sent.</t>
+</section>
+
+<section anchor="options.remoteMailboxes" title="remoteMailboxes">
+<t>The email-addresses where a copy of the incoming message is resent.</t>
+</section>
+</section>
+
+<vspace blankLines="10000" />
+
+<section anchor="procs" title="Configurable Procedures">
+<t>All of these procedures are defined in personal.tcl.
+You may override any of them in configFile.</t>
+
+<section anchor="procs.adminP" title="adminP">
+<figure>
+<artwork><![CDATA[
+ proc adminP {local domain}
+]]></artwork>
+</figure>
+
+<t>Returns "1" if the email-address is an automated administrative
+process.</t>
+</section>
+
+<section anchor="procs.friendP" title="friendP">
+<figure>
+<artwork><![CDATA[
+ proc friendP {local domain}
+]]></artwork>
+</figure>
+
+<t>Returns "1" if the email-address is from a
+<xref target="options.friendlyDomains">friendly domain</xref> or
+sub-domain.</t>
+</section>
+
+<section anchor="procs.ownerP" title="ownerP">
+<figure>
+<artwork><![CDATA[
+ proc ownerP {local domain}
+]]></artwork>
+</figure>
+
+<t>Returns "1" if the email-address refers to the user
+(as determined by looking at
+<xref target="options.myMailbox">myMailbox</xref>,
+<xref target="options.pdaMailboxes">pdaMailboxes</xref>, and
+<xref target="options.remoteMailboxes">remoteMailboxes</xref>.</t>
+</section>
+
+<section anchor="procs.saveMessage" title="saveMessage">
+<figure>
+<artwork><![CDATA[
+ proc saveMessage {inF {outF ""}}
+]]></artwork>
+</figure>
+
+<t>Saves a copy of the message contained in the file inF.
+If the destination file,
+outF,
+isn't specified,
+it defaults to the
+<xref target="options.defaultMaildrop">defaultMaildrop</xref>.</t>
+</section>
+
+<section anchor="procs.findPhrase" title="findPhrase">
+<figure>
+<artwork><![CDATA[
+ proc findPhrase {subject}
+]]></artwork>
+</figure>
+
+<t>Returns "1" if a previously-allocated passphrase is present in the
+subject.
+If so,
+the passphrase is forgotten.</t>
+</section>
+
+<section anchor="procs.makePhrase" title="makePhrase">
+<figure>
+<artwork><![CDATA[
+ proc makePhrase {}
+]]></artwork>
+</figure>
+
+<t>Returns an at-most-once passphrase for use with a rejection notice.</t>
+</section>
+
+<section anchor="procs.pruneDir" title="pruneDir">
+<figure>
+<artwork><![CDATA[
+ proc pruneDir {dir type}
+]]></artwork>
+</figure>
+
+<t>Removes old entries from one of the mailbot's
+<xref target="options.dataDirectory">databases</xref>.
+The second parameter is one of "addr", "msgid", or "phrase".</t>
+</section>
+
+<section anchor="procs.tclLog" title="tclLog">
+<figure>
+<artwork><![CDATA[
+ proc tclLog {message}
+]]></artwork>
+</figure>
+
+<t>Writes a message to the <xref target="options.logFile">logFile</xref>.</t>
+</section>
+</section>
+</section>
+
+</section>
+
+</middle>
+
+<back>
+<references />
+
+<section anchor="impersonal" title="Impersonal Mail">
+<t>If <xref target="procs.impersonalMail">impersonalMail</xref>
+returns a non-empty string
+then the message is processed differently than the algorithm given in
+<xref target="actions" />.
+Specifically:
+<list style="numbers">
+<t>If the message contains a previously-encountered "Message-ID",
+processing terminates.</t>
+
+<t>If the message's originator can not be determined,
+processing terminates.</t>
+
+<t>The value returned by
+<xref target="procs.impersonalMail">impersonalMail</xref>
+is the folder's name and is broken into one or more components
+seperated by dots (".").
+If there aren't at least two components,
+or if any of the components are empty
+(e.g., the folder is named "sys..announce"),
+then the message is bounced.</t>
+
+<t>If <xref target="options.mappingFile">mappingFile</xref> exists,
+that file is examined to see if an entry is present for the folder.
+If so,
+the message is processed according to the value present,
+one of:
+<list style="hanging">
+<t hangText=' "ignore":'>the message is silently ignored;</t>
+
+<t hangText=' "bounce":'>the message is noisily bounced; or,</t>
+
+<t hangText=" otherwise:">the message is resent to the address.</t>
+</list>
+Regardless,
+if an entry was present for the folder,
+then processing terminates.</t>
+
+<t>The message is <xref target="procs.saveMessage">saved</xref>
+in a file whose name is constructed by replacing each dot (".") in the
+folder name with a directory seperator
+(e.g., if the folder is named "sys.announce",
+then the file is called "announce" underneath the directory "sys"
+underneath the directory identified by
+<xref target="options.foldersDirectory">foldersDirectory</xref>.</t>
+
+<t>Finally,
+the file identified by <xref target="options.foldersFile">foldersFile</xref>
+is updated as necessary.</t>
+</list></t>
+
+<vspace blankLines="10000" />
+
+<section anchor="impersonal.options" title="Configuration Options">
+<t>If "impersonal" mail is received,
+then <xref target="options.foldersFile">foldersFile</xref> and
+<xref target="options.foldersDirectory">foldersDirectory</xref>
+must exist.</t>
+
+<section anchor="options.foldersDirectory" title="foldersDirectory">
+<t>The directory where the mailbot keeps private folders.</t>
+</section>
+
+<section anchor="options.foldersFile" title="foldersFile">
+<t>This file contains one line for each private folder.</t>
+</section>
+
+<section anchor="options.announceMailboxes" title="announceMailboxes">
+<t>The email-addresses where an announcement is sent when a new
+private folder is created.</t>
+</section>
+
+<section anchor="options.mappingFile" title="mappingFile">
+<t>The file consulted by the mailbot to determine how to process
+"impersonal" messages.
+Each line of the file consists of a folder name and value,
+seperated by a colon (":").
+There are three reserved values: "bounce", "ignore", and "store".</t>
+</section>
+</section>
+
+<vspace blankLines="10000" />
+
+<section anchor="impersonal.procs" title="Configurable Procedures">
+<t>All of these procedures are defined in personal.tcl.
+You may override any of them in configFile.</t>
+
+<section anchor="procs.impersonalMail" title="impersonalMail">
+<figure>
+<artwork><![CDATA[
+ proc impersonalMail {}
+]]></artwork>
+</figure>
+
+<t>If the message is deemed "impersonal",
+return the name of a corresponding private folder;
+otherwise,
+return the empty-string.</t>
+
+<t>Many mail systems have a mechanism of passing additional
+information when performing final delivery using a program.
+With modern versions of sendmail,
+for example,
+if mail is sent to a local user named "user+detail",
+then,
+in the absense of an alias for either "user+detail" or "user+*",
+then the message is delivered to "user".
+The trick is to get sendmail to pass the "detail" part to the mailbot.</t>
+
+<figure>
+<preamble>At present,
+sendmail passes this information only if procmail is your local
+mailer.
+Here's how I do it:</preamble>
+<artwork><![CDATA[
+ *** _alias.c Tue Dec 29 10:42:25 1998
+ --- alias.c Sat Sep 18 21:51:35 1999
+ ***************
+ *** 813,818 ****
+ --- 813,821 ----
+ define('z', user->q_home, e);
+ define('u', user->q_user, e);
+ define('h', user->q_host, e);
+ +
+ + setuserenv("SUFFIX", user->q_host);
+ +
+ if (ForwardPath == NULL)
+ ForwardPath = newstr("\201z/.forward");
+]]></artwork>
+<postamble>This makes available an environment variable called
+"SUFFIX" which has the "details" part.
+The drawback in this approach is that this information is lost if the
+message is re-queued for delivery
+(what's really needed is an addition to the .forward syntax to allow
+macros such as $h to be passed).</postamble>
+</figure>
+
+<figure>
+<preamble>The corresponding impersonalMail procedure is defined as:</preamble>
+<artwork><![CDATA[
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+]]></artwork>
+</figure>
+</section>
+
+<section anchor="procs.processFolder" title="processFolder">
+<figure>
+<artwork><![CDATA[
+ proc processFolder {folderName mimeT} { return $string }
+]]></artwork>
+</figure>
+
+<t>If an entry for the folder exists in the
+<xref target="options.mappingFile">mappingFile</xref>,
+and if the value for that entry is "process",
+then this procedure is invoked to return a string indicating what
+action to take
+(cf., <xref target="impersonal" />).</t>
+</section>
+</section>
+</section>
+
+<section title="An Example configFile">
+<figure>
+<preamble>Here is the ".forward" file for the user "hewes":</preamble>
+<artwork><![CDATA[
+ "|/usr/pkg/lib/mbot-1.1/personal.tcl
+ -config .personal/config.tcl -user hewes"
+]]></artwork>
+<postamble>(Of course, it's all on one line.)</postamble>
+</figure>
+
+<figure>
+<preamble>Here is the user's ".personal/config.tcl" file:</preamble>
+<artwork><![CDATA[
+ array set options [list \
+ dataDirectory .personal \
+ defaultMaildrop /var/mail/hewes \
+ auditInFile [file join .personal INCOMING] \
+ auditOutFile [file join .personal OUTGOING] \
+ friendlyDomains [list tcp.int example.com] \
+ logFile [file join .personal personal.log] \
+ myMailbox "Arlington Hewes <hewes@example.com>" \
+ pdaMailboxes hewes.pager@example.com \
+ noticeFile [file join .personal notice.txt] \
+ foldersDirectory [file join .personal folders] \
+ foldersFile [file join .personal .mailboxlist] \
+ announceMailboxes hewes+sys.announce@example.com \
+ mappingFile [file join .personal mapping] \
+ friendlyFire 1 \
+ dropNames [list *.bat *.exe *.src *.pif *.wav *.vbs] \
+ ]
+
+ proc impersonalMail {} {
+ global env
+
+ return $env(SUFFIX)
+ }
+]]></artwork>
+<postamble>Note that because
+<xref target="options.remoteMailboxes">remoteMailboxes</xref> isn't
+defined,
+personal messages are ultimately stored in the user's
+<xref target="options.defaultMaildrop">defaultMaildrop</xref>.</postamble>
+</figure>
+</section>
+
+<section title="Acknowledgements">
+<t>The original version of this mailbot was written by the author in 1994,
+implemented using the safe-tcl package
+(Borenstein and Rose, circa 1993).</t>
+</section>
+
+</back>
+
+</rfc>
diff --git a/tcllib/examples/mime/mbot/impersonal.tcl b/tcllib/examples/mime/mbot/impersonal.tcl
new file mode 100755
index 0000000..209baa6
--- /dev/null
+++ b/tcllib/examples/mime/mbot/impersonal.tcl
@@ -0,0 +1,531 @@
+#!/usr/bin/env tclsh
+##
+# impersonal.tcl - export impersonal mail via the web
+#
+# (c) 1999 Marshall T. Rose
+# Hold harmless the author, and any lawful use is allowed.
+#
+
+package require Tcl 8.3
+global options
+
+
+# begin of routines that may be redefined in configFile
+
+proc tclLog {message} {
+ global options
+
+ if {([info exists options(debugP)]) && ($options(debugP) > 0)} {
+ puts stderr $message
+ }
+
+ if {([string first "DEBUG " $message] == 0) \
+ || ([catch { set fd [open $options(logFile) \
+ { WRONLY CREAT APPEND }] }])} {
+ return
+ }
+
+ regsub -all "\n" $message " " message
+
+ catch { puts -nonewline $fd \
+ [format "%s %-8.8s %06d %s\n" \
+ [clock format [clock seconds] -format "%m/%d %T"] \
+ personal [expr {[pid]%65535}] $message] }
+
+ catch { close $fd }
+}
+
+# end of routines that may be redefined in configFile
+
+
+proc firstext {mime} {
+ array set props [mime::getproperty $mime]
+
+ if {[info exists props(parts)]} {
+ foreach part $props(parts) {
+ if {[string compare [firstext $part] ""]} {
+ return $part
+ }
+ }
+ } else {
+ switch -- $props(content) {
+ text/plain
+ -
+ text/html {
+ return $mime
+ }
+ }
+ }
+}
+
+proc sanitize {text} {
+ regsub -all "&" $text {\&amp;} text
+ regsub -all "<" $text {\&lt;} text
+
+ return $text
+}
+
+proc cleanup {{message ""} {code 500}} {
+ global errorCode errorInfo
+
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {[string compare $message ""]} {
+ tclLog $message
+
+ catch {
+ puts stdout "HTTP/1.0 $code Server Error
+Content-Type: text/html
+Status: 500 Server Error
+
+<html><head><title>Service Problem</title></head>
+<body><h1>Service Problem</h1>
+<b>Reason:</b> [sanitize $message]"
+
+ if {$code == 505} {
+ puts stdout "<br>
+<b>Stack:</b>
+<pre>[sanitize $einfo]</pre>
+<hr></hr>"
+ }
+
+ puts stdout "</body></html>"
+ }
+ }
+
+ flush stdout
+
+ exit 0
+}
+
+
+
+if {[catch {
+
+ set program impersonal
+
+ package require mbox 1.0
+ package require mutl 1.0
+ package require smtp 1.1
+ package require Tclx 8.0
+
+
+# move stdin, close stdin/stderr
+
+ dup [set null [open /dev/null { RDWR }]] stderr
+ set stdin [dup stdin]
+ dup $null stdin
+ close $null
+
+ fconfigure $stdin -translation crlf
+ fconfigure stdout -translation crlf
+
+
+# parse arguments and initialize environment
+
+ set program [file tail [file rootname $argv0]]
+
+ set configFile .${program}-config.tcl
+
+ set debugP 0
+
+ set userName ""
+
+ for {set argx 0} {$argx < $argc} {incr argx} {
+ set option [lindex $argv $argx]
+ if {[incr argx] >= $argc} {
+ cleanup "missing argument to $option"
+ }
+ set value [lindex $argv $argx]
+
+ switch -- $option {
+ -config {
+ set configFile $value
+ }
+
+ -debug {
+ set options(debugP) [set debugP [smtp::boolean $value]]
+ }
+
+ -user {
+ set userName $value
+ }
+
+ default {
+ cleanup "unknown option $option"
+ }
+ }
+ }
+
+ if {[string compare $userName ""]} {
+ if {[catch { id convert user $userName }]} {
+ cleanup "userName doesn't exist: $userName"
+ }
+ if {([catch { file isdirectory ~$userName } result]) \
+ || (!$result)} {
+ cleanup "userName doesn't have a home directory: $userName"
+ }
+
+ umask 0077
+ cd ~$userName
+ }
+
+ if {![file exists $configFile]} {
+ cleanup "configFile file doesn't exist: $configFile"
+ }
+ source $configFile
+
+ set options(debugP) $debugP
+
+ foreach {k v} [array get options] {
+ if {![string compare $v ""]} {
+ unset options($k)
+ }
+ }
+
+ foreach k [list dataDirectory foldersFile foldersDirectory] {
+ if {![info exists options($k)]} {
+ cleanup "configFile didn't define $k: $configFile"
+ }
+ }
+
+ if {![file isdirectory $options(dataDirectory)]} {
+ file mkdir $options(dataDirectory)
+ }
+
+
+# crack the request
+
+ set request ""
+ set eol ""
+ while {1} {
+ if {[catch { gets $stdin line } result]} {
+ cleanup "lost connection"
+ }
+ if {$result < 0} {
+ break
+ }
+
+ set gotP 0
+ foreach c [split $line ""] {
+ if {($c == " ") || ($c == "\t") || [ctype print $c]} {
+ if {!$gotP} {
+ append request $eol
+ set gotP 1
+ }
+ append request $c
+ }
+ }
+ if {!$gotP} {
+ break
+ }
+
+ set eol "\n"
+ }
+ set request [string tolower $request]
+
+ set getP 0
+ foreach param [split $request "\n"] {
+ if {[string first "get " $param] == 0} {
+ set getP 1
+ if {[catch { lindex [split $param " "] 1 } page]} {
+ cleanup "server supports only HTTP/1.0" 501
+ }
+ }
+ }
+ if {!$getP} {
+ cleanup "server supports only GET" 405
+ }
+
+ if {[string first /news? $page] != 0} {
+ cleanup "page $page unavailable" 504
+ }
+ foreach param [split [string range $page 6 end] &] {
+ if {[set x [string first = $param]] <= 0} {
+ cleanup "page $request unavailable" 504
+ }
+ set key [string range $param 0 [expr {$x-1}]]
+ set arg($key) [string range $param [expr {$x+1}] end]
+ }
+
+ set expires [mime::parsedatetime -now proper]
+
+
+# /news?index=newsgroups OR /news?index=recent
+
+ if {![catch { set arg(index) } index]} {
+ switch -- $index {
+ newsgroups {
+ set lastN 0
+ }
+
+ recent {
+ set lastN -1
+ }
+
+ default {
+ cleanup "page $request unavailable" 504
+ }
+ }
+ catch { set lastN $arg(lastn) }
+
+ if {[catch { open $options(foldersFile) { RDONLY } } fd]} {
+ cleanup $fd 505
+ }
+
+ set folders ""
+ set suffix [lindex [set prefix [file split \
+ $options(foldersDirectory)]] \
+ end]
+ set prefix [eval [list file join] [lreplace $prefix end end]]
+
+ for {set lineNo 1} {[gets $fd line] >= 0} {incr lineNo} {
+ if {[string first $suffix $line] != 0} {
+ continue
+ }
+ set file [file join $prefix $line]
+
+ if {[catch { file stat $file stat } result]} {
+ tclLog $result
+
+ continue
+ }
+ if {![string compare $stat(type) file]} {
+ lappend folders [list [eval [list file join] \
+ [lrange [file split $line] \
+ 1 end]] \
+ $stat(mtime)]
+ }
+ }
+
+ catch {close $fd }
+
+ switch -- $index {
+ recent {
+ set folders [lsort -integer -decreasing -index 1 $folders]
+ }
+
+ default {
+ set folders [lsort -dictionary -increasing -index 0 $folders]
+ }
+ }
+
+ puts stdout "HTTP/1.0 200
+Content-Type: text/html
+Pragma: no-cache
+Expires: $expires
+
+<html><head><title>newsgroups</title></head><body>
+<table cellborder=0 cellpadding=0 cellspacing=0>"
+
+ foreach entry $folders {
+ set folder [lindex $entry 0]
+ set t [fmtclock [set mtime [lindex $entry 1]] "%m/%d %H:%M"]
+
+ puts stdout "<tr><td><a href=\"news?folder=$folder&lastN=$lastN&mtime=$mtime\">$t</a></td><td width=5></td><td><b>$folder</b></td></tr>"
+ }
+
+ puts stdout "</table>
+</body></html>"
+
+ cleanup
+ }
+
+
+# /news?folder="whatever"
+
+ if {[catch { set arg(folder) } folder]} {
+ cleanup "page $request unavailable" 504
+ }
+
+ foreach p [file split $folder] {
+ if {(![string compare $p ""]) || ([string first . $p] >= 0)} {
+ cleanup "page $request unavailable" 504
+ }
+ }
+
+ set file [file join $options(foldersDirectory) $folder]
+ if {([catch { file type $file } type]) \
+ || ([string compare $type file])} {
+ cleanup "page $request unavailable" 504
+ }
+ if {[catch { mbox::initialize -file $file } mbox]} {
+ cleanup $mbox 505
+ }
+
+
+# /news?folder="whatever"&lastN="N"
+
+ if {![catch { set arg(lastn) } lastN]} {
+ array set props [mbox::getproperty $mbox]
+
+ if {$lastN < 0} {
+ set diff [expr {-($lastN*86400)}]
+
+ set last 0
+ for {set msgNo $props(last)} {$msgNo > 0} {incr msgNo -1} {
+ if {[catch { mbox::getmsgtoken $mbox $msgNo } mime]} {
+ tclLog $mime
+
+ continue
+ }
+
+ if {[catch { lindex [mime::getheader $mime Date] 0 } value]} {
+ set value ""
+ }
+ if {![catch { mime::parsedatetime $value rclock } rclock]} {
+ if {$rclock < $diff} {
+ if {$last == 0} {
+ set last $msgNo
+ }
+ set first $msgNo
+ }
+ if {$last == 0} {
+ break
+ }
+ }
+ }
+ if {$last > 0} {
+ set last $props(last)
+ }
+ } elseif {[set first \
+ [expr {[set last $props(last)]-($lastN+1)}]] <= 0} {
+ set first 1
+ }
+
+ puts stdout "HTTP/1.0 200
+Content-Type: text/html
+Pragma: no-cache
+Expires: $expires
+
+<html><head><title>$folder</title></head><body>"
+
+ if {$last == 0} {
+ puts stdout "<b>Empty.</b>
+</body></html>"
+
+ cleanup
+ }
+
+ puts stdout "<table cellborder=0 cellpadding=0 cellspacing=0>"
+ for {set msgNo $last} {$msgNo >= $first} {incr msgNo -1} {
+ if {[catch { mbox::getmsgtoken $mbox $msgNo } mime]} {
+ tclLog $mime
+
+ continue
+ }
+
+ set date ""
+ catch {
+ set value [lindex [mime::getheader $mime Date] 0]
+ append date [format %02d \
+ [mime::parsedatetime $value mon]] / \
+ [format %02d [mime::parsedatetime $value mday]] " " \
+ [format %02d [mime::parsedatetime $value hour]] : \
+ [format %02d [mime::parsedatetime $value min]]
+ }
+ if {![string compare $date ""]} {
+ set date "unknown date"
+ }
+
+ set from ""
+ catch {
+ set from [mutl::firstaddress [mime::getheader $mime From]]
+
+ catch { unset aprops }
+
+ array set aprops [lindex [mime::parseaddress $from] 0]
+ set from "<a href='mailto:$aprops(local)@$aprops(domain)'>$aprops(friendly)</a>"
+ }
+
+ set subject ""
+ catch {
+ set subject [lindex [mime::getheader $mime Subject] 0]
+ }
+
+ puts stdout "<tr><td><a href=\"news?folder=$folder&msgNo=$msgNo\">$date</a></td><td width=5></td><td><b>$from</b></td><td width=5></td><td>$subject</td></tr>"
+ }
+ puts stdout "</table>
+</body></html>"
+
+ cleanup
+ }
+
+
+# /news?folder="whatever"&msgNo="N"
+
+ if {![catch { set arg(msgno) } msgNo]} {
+ if {[catch { mbox::getmsgtoken $mbox $msgNo } mime]} {
+ cleanup $mime 505
+ }
+
+ if {![string compare [set part [firstext $mime]] ""]} {
+ set part $mime
+ }
+ switch -- [set content [mime::getproperty $part content]] {
+ text/plain {
+ regsub -all "\n\n" [mime::getbody $part] "<p>" body
+
+ set result "<html><head><title>$folder $msgNo</title></head>
+<body>$body</body></html>"
+
+ }
+
+ text/html {
+ set result [mime::getbody $part]
+ }
+
+ default {
+ set result "<html><head><title>$folder $msgNo</title></head>
+<body>
+Message is $content.
+</body></html>"
+ }
+ }
+
+ puts stdout "HTTP/1.0 200
+Content-Type: text/html
+
+$result"
+
+ cleanup
+ }
+
+
+ cleanup "page $request unavailable" 504
+
+
+} result]} {
+ global errorCode errorInfo
+
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {(![catch { info body tclLog } result2]) \
+ && ([string compare [string trim $result2] \
+ {catch {puts stderr $string}}])} {
+ catch { tclLog $result }
+ }
+
+ if {![string first "POSIX EPIPE" $ecode]} {
+ exit 0
+ }
+
+ catch {
+ smtp::sendmessage \
+ [mime::initialize \
+ -canonical text/plain \
+ -param {charset us-ascii} \
+ -string "$result\n\nerrorCode: $ecode\n\n$einfo"] \
+ -originator "" \
+ -header [list From [id user]@[info hostname]] \
+ -header [list To operator@[info hostname]] \
+ -header [list Subject "[info hostname] fatal $program"]
+ }
+
+ cleanup $result
+}
+
+
+exit 75
diff --git a/tcllib/examples/mime/mbot/mbox.tcl b/tcllib/examples/mime/mbot/mbox.tcl
new file mode 100644
index 0000000..01956a0
--- /dev/null
+++ b/tcllib/examples/mime/mbot/mbox.tcl
@@ -0,0 +1,465 @@
+# mbox.tcl - mailbox package
+#
+# (c) 1999 Marshall T. Rose
+# Hold harmless the author, and any lawful use is allowed.
+#
+
+#
+# TODO:
+#
+# mbox::initialize
+# add -pop server option
+# add -imap server option
+# along with -username, -password, and -passback
+#
+# mbox::getmsgproperty
+# add support for deleted messages
+#
+# mbox::deletemsg token msgNo
+# marks a message for deletion
+#
+# mbox::synchronize token ?-commit boolean?
+# commits or rollllbacks changes
+
+
+package provide mbox 1.0
+
+package require mime 1.1
+
+
+#
+# state variables:
+#
+# msgs: serialized array of messages, containing array of:
+# msgNo, mime
+# count: number of messages
+# first: number of initial message
+# last: number of final message
+# value: either "file", or "directory"
+#
+# file: file containing mailbox
+# fd: corresponding file descriptor
+# fileA: serialized array of messages, containing array of:
+# msgNo, offset, size
+#
+# directory: directory containing mailbox
+# dirA: serialized array of messages, containing array of:
+# msgNo, size
+#
+
+namespace eval mbox {
+ variable mbox
+ array set mbox { uid 0 }
+
+ namespace export initialize finalize getproperty \
+ getmsgtoken getmsgproperty
+}
+
+
+proc mbox::initialize {args} {
+ global errorCode errorInfo
+
+ variable mbox
+
+ set token [namespace current]::[incr mbox(uid)]
+
+ variable $token
+ upvar 0 $token state
+
+ if {[set code [catch { eval [list mbox::initializeaux $token] $args } \
+ result]]} {
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ catch { mbox::finalize $token -subordinates dynamic }
+
+ return -code $code -errorinfo $einfo -errorcode $ecode $result
+ }
+
+ return $token
+}
+
+
+proc mbox::initializeaux {token args} {
+ variable $token
+ upvar 0 $token state
+
+ set state(msgs) ""
+ set state(count) 0
+ set state(first) 0
+ set state(last) 0
+
+ set argc [llength $args]
+ for {set argx 0} {$argx < $argc} {incr argx} {
+ set option [lindex $args $argx]
+ if {[incr argx] >= $argc} {
+ error "missing argument to $option"
+ }
+ set value [lindex $args $argx]
+
+ switch -- $option {
+ -directory {
+ set state(directory) $value
+ }
+
+ -file {
+ set state(file) $value
+ }
+
+ default {
+ error "unknown option $option"
+ }
+ }
+ }
+
+ set valueN 0
+ foreach value [list directory file] {
+ if {[info exists state($value)]} {
+ set state(value) $value
+ incr valueN
+ }
+ }
+ if {$valueN != 1} {
+ error "specify exactly one of -directory, or -file"
+ }
+
+ return [mbox::initialize_$state(value) $token]
+}
+
+
+proc mbox::initialize_file {token} {
+ variable $token
+ upvar 0 $token state
+
+ fconfigure [set state(fd) [open $state(file) { RDONLY }]] \
+ -translation binary
+
+ array set fileA ""
+ set msgNo 0
+
+ if {[gets $state(fd) line] < 0} {
+ return $token
+ }
+ switch -regexp -- $line {
+ "^From " {
+ set format Mailx
+ set preB "From "
+
+ set phase ""
+ }
+
+ "\01\01\01\01" {
+ set format MMDF
+ set preB "\01\01\01\01"
+ set postB "\01\01\01\01"
+
+ if {([gets $state(fd) line] >= 0) \
+ && ([string first "From MAILER-DAEMON " $line] == 0)} {
+ set phase skip
+ } else {
+ set phase pre
+ }
+ }
+
+ default {
+ error "unrecognized mailbox format"
+ }
+ }
+ seek $state(fd) 0 start
+
+ while {[gets $state(fd) line] >= 0} {
+ switch -- $format/$phase {
+ Mailx/ {
+ if {[string first $preB $line] == 0} {
+ if {$msgNo > 0} {
+ set fileA($msgNo) [list msgNo $msgNo offset $offset \
+ size $size]
+ }
+
+ incr msgNo
+ set offset [tell $state(fd)]
+ set size 0
+ } else {
+ incr size [expr {[string length $line]+1}]
+ }
+ }
+
+ MMDF/pre {
+ if {![string compare $preB $line]} {
+ incr msgNo
+ set offset [tell $state(fd)]
+ set size 0
+
+ set phase post
+ } else {
+ error "invalid mailbox"
+ }
+ }
+
+ MMDF/post {
+ if {![string compare $postB $line]} {
+ set fileA($msgNo) [list msgNo $msgNo offset $offset \
+ size $size]
+
+ set phase pre
+ } else {
+ incr size [expr {[string length $line]+1}]
+ }
+ }
+
+ MMDF/skip {
+ if {![string compare $preB $line]} {
+ set phase skip2
+ }
+ }
+
+ MMDF/skip2 {
+ if {![string compare $postB $line]} {
+ set phase pre
+ }
+ }
+ }
+ }
+
+ switch -- $format/$phase {
+ Mailx/ {
+ if {$msgNo > 0} {
+ set fileA($msgNo) [list msgNo $msgNo offset $offset \
+ size $size]
+ }
+ }
+
+ MMDF/post
+ -
+ MMDF/skip2 {
+ error "incomplete mailbox"
+ }
+ }
+
+ set state(fileA) [array get fileA]
+ if {[set state(last) [set state(count) $msgNo]] > 0} {
+ set state(first) 1
+ }
+
+ return $token
+}
+
+
+proc mbox::initialize_directory {token} {
+ variable $token
+ upvar 0 $token state
+
+ array set dirA ""
+
+ set first 0
+ set last 0
+ foreach file [glob -nocomplain [file join $state(directory) *]] {
+ if {(![regexp {^[1-9][0-9]*$} [set msgNo [file tail $file]]]) \
+ || ([catch { file size $file } size])} {
+ continue
+ }
+
+ if {($first == 0) || ($msgNo < $first)} {
+ set first $msgNo
+ }
+ if {$last < $msgNo} {
+ set last $msgNo
+ }
+
+ set dirA($msgNo) [list msgNo $msgNo size $size]
+ incr state(count)
+ }
+
+ set state(dirA) [array get dirA]
+ if {[set state(last) $last] > 0} {
+ set state(first) $first
+ }
+
+ return $token
+}
+
+proc mbox::finalize {token args} {
+ variable $token
+ upvar 0 $token state
+
+ array set options [list -subordinates dynamic]
+ array set options $args
+
+ switch -- $options(-subordinates) {
+ all
+ -
+ dynamic {
+ array set msgs $state(msgs)
+
+ for {set msgNo $state(first)} \
+ {$msgNo <= $state(last)} \
+ {incr msgNo} {
+ if {![catch { array set msg $msgs($msgNo) }]} {
+ eval [list mime::finalize $msg(mime)] $args
+ }
+ }
+ }
+
+ none {
+ }
+
+ default {
+ error "unknown value for -subordinates $options(-subordinates)"
+ }
+ }
+
+ if {[info exists state(fd)]} {
+ catch { close $state(fd) }
+ }
+
+ foreach name [array names state] {
+ unset state($name)
+ }
+ unset $token
+}
+
+
+proc mbox::getproperty {token {property ""}} {
+ variable $token
+ upvar 0 $token state
+
+ switch -- $property {
+ "" {
+ return [list count $state(count) \
+ first $state(first) \
+ last $state(last) \
+ messages [mbox::getmessages $token]]
+ }
+
+ -names {
+ return [list count first last messages]
+ }
+
+ count
+ -
+ first
+ -
+ last {
+ return $state($property)
+ }
+
+ messages {
+ return [mbox::getmessages $token]
+ }
+
+ default {
+ error "unknown property $property"
+ }
+ }
+}
+
+
+proc mbox::getmessages {token} {
+ variable $token
+ upvar 0 $token state
+
+ switch -- $state(value) {
+ directory {
+ array set msgs $state(dirA)
+ }
+
+ file {
+ array set msgs $state(fileA)
+ }
+ }
+
+ return [lsort -integer [array names msgs]]
+}
+
+
+proc mbox::getmsgtoken {token msgNo} {
+ variable $token
+ upvar 0 $token state
+
+ if {($msgNo < $state(first)) || ($msgNo > $state(last))} {
+ error "message number out of range: $state(first)..$state(last)"
+ }
+
+ array set msgs $state(msgs)
+ if {![catch { array set msg $msgs($msgNo) }]} {
+ return $msg(mime)
+ }
+
+ switch -- $state(value) {
+ directory {
+ set mime [mime::initialize \
+ -file [file join $state(directory) $msgNo]]
+ }
+
+ file {
+ array set fileA $state(fileA)
+ array set msg $fileA($msgNo)
+ set mime [mime::initialize -file $state(file) -root $token \
+ -offset $msg(offset) -count $msg(size)]
+ }
+ }
+
+ set msgs($msgNo) [list msgNo $msgNo mime $mime]
+ set state(msgs) [array get msgs]
+
+ return $mime
+}
+
+
+proc mbox::getmsgproperty {token msgNo {property ""}} {
+ variable $token
+ upvar 0 $token state
+
+ if {($msgNo < $state(first)) || ($msgNo > $state(last))} {
+ error "message number out of range: $state(first)..$state(last)"
+ }
+
+ switch -- $state(value) {
+ directory {
+ array set dirA $state(dirA)
+ if {[catch { array set msg $dirA($msgNo) }]} {
+ error "message $msgNo doesn't exist"
+ }
+ }
+
+ file {
+ array set fileA $state(fileA)
+ array set msg $fileA($msgNo)
+ }
+ }
+
+ set props [list flags size uidl]
+
+ switch -- $property {
+ "" {
+ array set properties ""
+
+ foreach prop $props {
+ if {[info exists msg($prop)]} {
+ set properties($prop) $msg($prop)
+ }
+ }
+
+ return [array get properties]
+ }
+
+ -names {
+ set names ""
+ foreach prop $props {
+ if {[info exists msg($prop)]} {
+ lappend names $prop
+ }
+ }
+
+ return $names
+ }
+
+ default {
+ if {[lsearch -exact $props $property] < 0} {
+ error "unknown property $property"
+ }
+
+ return $msg($property)
+ }
+ }
+}
diff --git a/tcllib/examples/mime/mbot/mutl.tcl b/tcllib/examples/mime/mbot/mutl.tcl
new file mode 100644
index 0000000..c918126
--- /dev/null
+++ b/tcllib/examples/mime/mbot/mutl.tcl
@@ -0,0 +1,123 @@
+# mutl.tcl - messaging utilities
+#
+# (c) 1999 Marshall T. Rose
+# Hold harmless the author, and any lawful use is allowed.
+#
+
+
+package provide mutl 1.0
+
+
+namespace eval mutl {
+ namespace export exclfile tmpfile \
+ firstaddress gathertext getheader
+}
+
+
+proc mutl::exclfile {fileN {stayP 0}} {
+ global errorCode errorInfo
+
+ for {set i 0} {$i < 10} {incr i} {
+ if {![catch { set xd [open $fileN { RDWR CREAT EXCL }] } result]} {
+ if {(![set code [catch { puts $xd [expr {[pid]%65535}]
+ flush $xd } result]]) \
+ && (!$stayP)} {
+ if {![set code [catch { close $xd } result]]} {
+ set xd ""
+ }
+ }
+
+ if {$code} {
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ catch { close $xd }
+
+ file delete -- $fileN
+
+ return -code $code -errorinfo $einfo -errorcode $ecode $result
+ }
+
+ return $xd
+ }
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {(([llength $ecode] != 3) \
+ || ([string compare [lindex $ecode 0] POSIX]) \
+ || ([string compare [lindex $ecode 1] EEXIST]))} {
+ return -code 1 -errorinfo $einfo -errorcode $ecode $result
+ }
+
+ after 1000
+ }
+
+ error "unable to exclusively open $fileN"
+}
+
+proc mutl::tmpfile {prefix {tmpD ""}} {
+ global env
+ global errorCode errorInfo
+
+ if {(![string compare $tmpD ""]) && ([catch { set tmpD $env(TMP) }])} {
+ set tmpD /tmp
+ }
+ set file [file join $tmpD $prefix]
+
+ append file [expr {[pid]%65535}]
+
+ for {set i 0} {$i < 10} {incr i} {
+ if {![set code [catch { set fd [open $file$i \
+ { WRONLY CREAT EXCL }] } \
+ result]]} {
+ return [list file $file$i fd $fd]
+ }
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {(([llength $ecode] != 3) \
+ || ([string compare [lindex $ecode 0] POSIX]) \
+ || ([string compare [lindex $ecode 1] EEXIST]))} {
+ return -code $code -errorinfo $einfo -errorcode $ecode $result
+ }
+ }
+
+ error "unable to create temporary file"
+}
+
+proc mutl::firstaddress {values} {
+ foreach value $values {
+ foreach addr [mime::parseaddress $value] {
+ catch { unset aprops }
+ array set aprops $addr
+
+ if {[string compare $aprops(proper) ""]} {
+ return $aprops(proper)
+ }
+ }
+ }
+}
+
+proc mutl::gathertext {token} {
+ array set props [mime::getproperty $token]
+
+ set text ""
+
+ if {[info exists props(parts)]} {
+ foreach part $props(parts) {
+ append text [mutl::gathertext $part]
+ }
+ } elseif {![string compare $props(content) text/plain]} {
+ set text [mime::getbody $token]
+ }
+
+ return $text
+}
+
+proc mutl::getheader {token key} {
+ if {[catch { mime::getheader $token $key } result]} {
+ set result ""
+ }
+
+ return $result
+}
diff --git a/tcllib/examples/mime/mbot/personal.tcl b/tcllib/examples/mime/mbot/personal.tcl
new file mode 100755
index 0000000..bf16a0b
--- /dev/null
+++ b/tcllib/examples/mime/mbot/personal.tcl
@@ -0,0 +1,982 @@
+#!/usr/bin/env tclsh
+## -*- tcl -*-
+# personal.tcl - process personal mail
+#
+# (c) 1999 Marshall T. Rose
+# Hold harmless the author, and any lawful use is allowed.
+#
+# The original version was written in 1994!
+#
+
+package require Tcl 8.3
+
+global options
+
+
+# begin of routines that may be redefined in configFile
+
+proc impersonalMail {originator} {}
+
+proc adminP {local domain} {
+ set local [string tolower $local]
+
+ foreach lhs [list administrator \
+ archive-server \
+ daemon \
+ failrepter \
+ faxmaster \
+ gateway \
+ listmaster \
+ listproc \
+ lotus_mail_exchange \
+ m400 \
+ *mailer* \
+ *maiser* \
+ mmdf \
+ mrgate \
+ mx-mailer-daemon \
+ numbers-info-forw \
+ postman* \
+ *postmast* \
+ pp \
+ smtp \
+ sysadmin \
+ ucx_smtp \
+ uucp] {
+ if {[string match $lhs $local]} {
+ return 1
+ }
+ }
+
+ return 0
+}
+
+proc friendP {local domain} {
+ global options
+
+ if {![info exists options(friendlyDomains)]} {
+ return 0
+ }
+
+ set domain [string tolower $domain]
+
+ foreach rhs $options(friendlyDomains) {
+ if {(![string compare $rhs $domain]) \
+ || ([string match *.$rhs $domain])} {
+ return 1
+ }
+ }
+
+ return 0
+}
+
+proc ownerP {local domain} {
+ global options
+
+ foreach mailbox {myMailbox pdaMailboxes remoteMailboxes} {
+ if {![info exists options($mailbox)]} {
+ continue
+ }
+
+ foreach addr [mime::parseaddress $options($mailbox)] {
+ catch { unset aprops }
+
+ array set aprops $addr
+ if {![string compare [string tolower $local@$domain] \
+ [string tolower $aprops(local)@$aprops(domain)]]} {
+ return 1
+ }
+ }
+ }
+
+ return 0
+}
+
+# the algorithm below is for systems that use the MMDF/MH convention
+
+proc saveMessage {inF {outF ""}} {
+ global errorCode errorInfo
+ global options
+
+ set inC [open $inF { RDONLY }]
+
+ if {![string compare $outF ""]} {
+ set outF $options(defaultMaildrop)
+ }
+ mutl::exclfile [set lockF $outF.lock]
+
+ set code [catch { set outC [open $outF { WRONLY CREAT APPEND }] } result]
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {!$code} {
+ set code [catch {
+ puts $outC [set boundary "\001\001\001\001"]
+ puts $outC "Delivery-Date: [mime::parsedatetime -now proper]"
+
+ while {[gets $inC line] >= 0} {
+ if {[string compare $boundary $line]} {
+ puts $outC $line
+ } else {
+ puts $outC "\002\001\001\001"
+ }
+ }
+
+ puts $outC $boundary
+ } result]
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {[catch { close $outC } result2]} {
+ tclLog $result2
+ }
+ }
+
+ file delete -- $lockF
+
+ if {[catch { close $inC } result2]} {
+ tclLog $result2
+ }
+
+ return -code $code -errorinfo $einfo -errorcode $ecode $result
+}
+
+proc findPhrase {subject} {
+ global options
+
+ set subject [string toupper $subject]
+
+ foreach file [glob -nocomplain [file join $options(dataDirectory) \
+ phrases *]] {
+ if {[catch { otp_words -mode encode \
+ [base64 -mode decode -- \
+ [join [split [file tail $file] _] /]] } \
+ phrase]} {
+ tclLog "$file: $phrase"
+ } elseif {[string first $phrase $subject] >= 0} {
+ if {[catch { file delete -- $file } result]} {
+ tclLog $result
+ }
+
+ return 1
+ }
+ }
+
+ return 0
+}
+
+proc makePhrase {} {
+ global options
+
+ if {![file isdirectory \
+ [set phraseD [file join $options(dataDirectory) phrases]]]} {
+ file mkdir $phraseD
+ } else {
+ pruneDir $phraseD phrase
+ }
+
+ set key [mime::uniqueID]
+ set seqno 8
+ while {[incr seqno -1] >= 0} {
+ set key [otp_md5 -- $key]
+ }
+
+ set phraseF [file join $phraseD \
+ [join [split [string trim \
+ [base64 -mode encode -- $key]] /] _]]
+ if {[catch { close [open $phraseF { WRONLY CREAT TRUNC }] } result]} {
+ tclLog $result
+ }
+
+ return [otp_words -mode encode -- $key]
+}
+
+proc pruneDir {dir type} {
+ switch -- $type {
+ addr {
+ set days 14
+ }
+
+ msgid {
+ set days 28
+ }
+
+ phrase {
+ set days 7
+ }
+ }
+
+ set then [expr {[clock seconds]-($days*86400)}]
+
+ foreach file [glob -nocomplain [file join $dir *]] {
+ if {(![catch { file mtime $file } result]) \
+ && ($result < $then) \
+ && ([catch { file delete -- $file } result])} {
+ tclLog $result
+ }
+ }
+}
+
+proc tclLog {message} {
+ global options
+
+ if {([info exists options(debugP)]) && ($options(debugP) > 0)} {
+ puts stderr $message
+ }
+
+ if {([string first "DEBUG " $message] == 0) \
+ || ([catch { set fd [open $options(logFile) \
+ { WRONLY CREAT APPEND }] }])} {
+ return
+ }
+
+ regsub -all "\n" $message " " message
+
+ catch { puts -nonewline $fd \
+ [format "%s %-8.8s %06d %s\n" \
+ [clock format [clock seconds] -format "%m/%d %T"] \
+ personal [expr {[pid]%65535}] $message] }
+
+ catch { close $fd }
+}
+
+# end of routines that may be redefined in configFile
+
+
+global deleteFiles
+
+set deleteFiles {}
+
+proc cleanup {{message ""} {status 75}} {
+ global deleteFiles
+
+ foreach file $deleteFiles {
+ if {[catch { file delete -- $file } result]} {
+ tclLog $result
+ }
+ }
+
+ if {[string compare $message ""]} {
+ tclLog $message
+ exit $status
+ }
+
+ exit 0
+}
+
+proc dofolder {folder inF} {
+ global options
+
+ catch { unset aprops }
+
+ array set aprops [lindex [mime::parseaddress $folder] 0]
+ set folder [join [split $aprops(local) /] _]
+
+ if {[set folderN [llength [set folderL [split $folder .]]]] <= 1} {
+ cleanup "invalid folder: $folder"
+ }
+
+ foreach f $folderL {
+ if {![string compare $f ""]} {
+ cleanup "invalid folder: $folder" 67
+ }
+ }
+
+ if {![file isdirectory \
+ [set articleD [eval [list file join \
+ $options(foldersDirectory)] \
+ [lrange $folderL 0 \
+ [expr {$folderN-2}]]]]]} {
+ file mkdir $articleD
+ }
+ if {![file exists [set articleF [file join $articleD \
+ [lindex $folderL \
+ [expr {$folderN-1}]]]]]} {
+ set newP 1
+ } else {
+ set newP 0
+ }
+
+ set fd [open $options(foldersFile) { RDWR CREAT }]
+ set fl "\n[read $fd]"
+
+ set dir [lindex [file split $options(foldersDirectory)] end]
+ if {[string first "\n$dir\n" $fl] < 0} {
+ puts $fd $dir
+ }
+ foreach f $folderL {
+ set dir [file join $dir $f]
+ if {[string first "\n$dir\n" $fl] < 0} {
+ puts $fd $dir
+ }
+ }
+
+ close $fd
+
+ if {[catch { saveMessage $inF $articleF } result]} {
+ cleanup "unable to save message in $articleF: $result"
+ }
+
+ if {($newP) && ([info exists options(announceMailboxes)])} {
+ if {[catch { smtp::sendmessage \
+ [mime::initialize \
+ -canonical text/plain \
+ -param {charset us-ascii} \
+ -string ""] \
+ -atleastone true \
+ -originator "" \
+ -header [list From $options(myMailbox)] \
+ -header [list To $options(announceMailboxes)] \
+ -header [list Subject "new folder $folder"] } \
+ result]} {
+ tclLog $result
+ }
+ }
+}
+
+proc alladdrs {mime keys} {
+ set result {}
+
+ foreach key $keys {
+ foreach value [mutl::getheader $mime $key] {
+ foreach addr [mime::parseaddress $value] {
+ lappend result $addr
+ }
+ }
+ }
+
+ return $result
+}
+
+proc anyfriend {outD addrs} {
+ global options
+
+ if {!$options(friendlyFire)} {
+ return ""
+ }
+
+ foreach addr $addrs {
+ catch { unset aprops }
+
+ array set aprops $addr
+ if {[catch { string tolower $aprops(local)@$aprops(domain) } \
+ recipient]} {
+ continue
+ }
+
+ if {[ownerP $aprops(local) $aprops(domain)]} {
+ tclLog "DEBUG: skipping $recipient"
+ continue
+ }
+
+ set outF [file join $outD [join [split $recipient /] _]]
+ if {[file exists $outF]} {
+ return $recipient
+ }
+
+ tclLog "DEBUG: unknown recipient $recipient"
+ }
+
+ return ""
+}
+
+
+if {[catch {
+
+ set program personal
+
+ package require mutl 1.0
+ package require smtp 1.1
+ package require Tclx 8.0
+
+
+# parse arguments and initialize environment
+
+ set program [file tail [file rootname $argv0]]
+
+ set configFile .${program}-config.tcl
+
+ set debugP 0
+
+ set messageFile -
+
+ set originatorAddress ""
+
+ set userName ""
+
+ for {set argx 0} {$argx < $argc} {incr argx} {
+ set option [lindex $argv $argx]
+ if {[incr argx] >= $argc} {
+ cleanup "missing argument to $option"
+ }
+ set value [lindex $argv $argx]
+
+ switch -- $option {
+ -config {
+ set configFile $value
+ }
+
+ -debug {
+ set options(debugP) [set debugP [smtp::boolean $value]]
+ }
+
+ -file {
+ set messageFile $value
+ }
+
+ -originator {
+ set originatorAddress $value
+ }
+
+ -user {
+ set userName $value
+ }
+
+ default {
+ cleanup "unknown option $option"
+ }
+ }
+ }
+
+ if {![string compare $messageFile -]} {
+ array set tmp [mutl::tmpfile personal]
+
+ lappend deleteFiles [set messageFile $tmp(file)]
+
+ catch { file attributes $messageFile -permissions 0600 }
+
+ if {[gets stdin line] <= 0} {
+ cleanup "empty message"
+ }
+ if {[string first "From " $line] == 0} {
+ if {![string compare $originatorAddress ""]} {
+ set line [string range $line 5 end]
+ if {[set x [string first " " $line]] > 0} {
+ set originatorAddress [string range $line 0 [expr {$x-1}]]
+ }
+ }
+ } else {
+ puts $tmp(fd) $line
+ }
+ fcopy stdin $tmp(fd)
+ close $tmp(fd)
+ }
+
+ if {[string compare $userName ""]} {
+ if {[catch { id convert user $userName }]} {
+ cleanup "userName doesn't exist: $userName"
+ }
+ if {([catch { file isdirectory ~$userName } result]) \
+ || (!$result)} {
+ cleanup "userName doesn't have a home directory: $userName"
+ }
+
+ umask 0077
+ cd ~$userName
+ }
+
+ if {![file exists $configFile]} {
+ cleanup "configFile file doesn't exist: $configFile"
+ }
+ source $configFile
+
+ set options(debugP) $debugP
+
+ foreach {k v} [array get options] {
+ if {![string compare $v ""]} {
+ unset options($k)
+ }
+ }
+
+ foreach k [list dataDirectory defaultMaildrop] {
+ if {![info exists options($k)]} {
+ cleanup "configFile didn't define $k: $configFile"
+ }
+ }
+
+ if {![file isdirectory $options(dataDirectory)]} {
+ file mkdir $options(dataDirectory)
+ }
+
+ if {![info exists options(myMailbox)]} {
+ set options(myMailbox) [id user]
+ }
+
+ if {![info exists options(friendlyFire)]} {
+ set options(friendlyFire) 0
+ }
+
+
+# crack the message
+
+ if {[catch { set mime [mime::initialize -file $messageFile] } result]} {
+# global errorCode errorInfo
+#
+# set ecode $errorCode
+# set einfo $errorInfo
+#
+# if {![catch {
+# smtp::sendmessage \
+# [mime::initialize \
+# -canonical multipart/mixed \
+# -parts [list [mime::initialize \
+# -canonical text/plain \
+# -param {charset us-ascii} \
+# -string "$result\n\nerrorCode: $ecode\n\n$einfo"] \
+# [mime::initialize \
+# -canonical application/octet-stream \
+# -file $messageFile]]] \
+# -originator "" \
+# -header [list From $options(myMailbox)] \
+# -header [list To $options(myMailbox)] \
+# -header [list Subject "[info hostname] alert $program"]
+# }]} {
+# set result ""
+# }
+
+ if {[info exists options(auditInFile)]} {
+ saveMessage $messageFile $options(auditInFile)
+ tclLog "invalid, but saved: $result"
+ cleanup
+ }
+
+ cleanup "re-queued: $result"
+ }
+
+ set origProper ""
+ foreach key {From Sender Return-Path} {
+ if {[string compare \
+ [set origProper [mutl::firstaddress \
+ [mutl::getheader $mime $key]]] \
+ ""]} {
+ break
+ }
+ }
+ if {![string compare $origProper ""]} {
+ set origProper [mutl::firstaddress [list $originatorAddress]]
+ }
+
+ catch { unset aprops }
+
+ array set aprops [list local "" domain ""]
+ array set aprops [lindex [mime::parseaddress $origProper] 0]
+ set origLocal $aprops(local)
+ set origDomain $aprops(domain)
+
+ regsub -all " *" \
+ [set subject [string trim \
+ [lindex [mutl::getheader $mime Subject] 0]]] \
+ " " subject
+
+
+ if {[catch { set folderTarget [impersonalMail $origLocal@$origDomain] }]} {
+ set folderTarget ""
+ }
+ if {[set impersonalP [string compare $folderTarget ""]]} {
+ if {![info exists options(foldersDirectory)]} {
+ cleanup "configFile didn't define folderTarget: $configFile"
+ }
+ } elseif {[info exists options(auditInFile)]} {
+# keep an audit copy of personal mail
+
+ saveMessage $messageFile $options(auditInFile)
+ }
+
+
+# perform duplicate supression
+
+ set messageID [lindex [concat [mutl::getheader $mime Resent-Message-ID] \
+ [mutl::getheader $mime Message-ID]] 0]
+ if {[string compare $messageID ""]} {
+ if {![file isdirectory \
+ [set idD [file join $options(dataDirectory) msgids]]]} {
+ file mkdir $idD
+ } else {
+ pruneDir $idD msgid
+ }
+
+ if {[set len [string length $messageID]] > 2} {
+ set messageID [string range $messageID 1 [expr {$len-2}]]
+ }
+ if {$impersonalP} {
+ set prefix X-
+
+ catch { unset aprops }
+
+ array set aprops [lindex [mime::parseaddress $folderTarget] 0]
+ set prefix \
+ X-[lindex [split [join [split $aprops(local) /] _] .] 0]-
+ } else {
+ set prefix ""
+ }
+
+ set idF [file join $idD $prefix[join [split $messageID /] _]]
+ if {[file exists $idF]} {
+ tclLog "duplicate ID: $origProper $messageID ($subject)"
+
+ cleanup
+ }
+
+ if {[catch { close [open $idF { WRONLY CREAT TRUNC }] } result]} {
+ tclLog $result
+ }
+ }
+
+
+# record information about the originator
+
+ if {![string compare \
+ [set origAddress \
+ [string tolower $origLocal@$origDomain]] \
+ @]} {
+ tclLog "no originator"
+
+ if {!$impersonalP} {
+ saveMessage $messageFile
+ }
+
+ cleanup
+ }
+
+ tclLog "DEBUG processing: $origProper <$messageID> ($subject)"
+
+ if {![file isdirectory \
+ [set inD [file join $options(dataDirectory) inaddrs]]]} {
+ file mkdir $inD
+ }
+
+ set inF [file join $inD [join [split $origAddress /] _]]
+ if {[catch { set fd [open $inF { WRONLY CREAT TRUNC }] } result]} {
+ tclLog $result
+ } else {
+ catch { puts $fd $origProper }
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+ }
+
+
+# store impersonal mail in private folder area
+
+ if {$impersonalP} {
+ if {![string compare $messageID ""]} {
+ cleanup "no Message-ID"
+ }
+
+ if {![file isdirectory $options(foldersDirectory)]} {
+ file mkdir $foldersDirectory
+ }
+
+ array set mapping {}
+
+ if {![catch { set fd [open $options(mappingFile) { RDONLY }] }]} {
+ while {[gets $fd line] >= 0} {
+ if {([llength [set map [split $line :]]] == 2) \
+ && ([string length \
+ [set k [string trim [lindex $map 0]]]] \
+ > 0) \
+ && ([string length \
+ [set v [string trim [lindex $map 1]]]] \
+ > 0)} {
+ set mapping($k) $v
+ }
+ }
+
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+ }
+
+ if {![info exists mapping($folderTarget)]} {
+ set mapping($folderTarget) store
+ }
+ if {![string compare $mapping($folderTarget) process]} {
+ catch { set mapping($folderTarget) \
+ [processFolder $folderTarget $mime] }
+ }
+ switch -- $mapping($folderTarget) {
+ store {
+ dofolder $folderTarget $messageFile
+ }
+
+ ignore {
+ tclLog "ignoring message for $folderTarget"
+ }
+
+ bounce {
+ cleanup "rejecting message for $folderTarget" 67
+ }
+
+ default {
+ if {[catch { smtp::sendmessage $mime \
+ -atleastone true \
+ -originator "" \
+ -recipients $mapping($folderTarget) } \
+ result]} {
+ tclLog $result
+ }
+ }
+ }
+
+ cleanup
+ }
+
+
+# perform originator supression and guest list maintenance
+
+ if {[string compare \
+ [set resentProper \
+ [mutl::firstaddress \
+ [mutl::getheader $mime Resent-From]]] \
+ ""]} {
+ catch { unset aprops }
+
+ array set aprops [lindex [mime::parseaddress $resentProper] 0]
+ set resentLocal $aprops(local)
+ set resentDomain $aprops(domain)
+
+ if {[string compare \
+ [set resentAddress \
+ [string tolower $resentLocal@$resentDomain]] \
+ @]} {
+ foreach p {Proper Local Domain Address} {
+ set orig$p [set resent$p]
+ }
+ }
+ }
+
+ foreach p {out tmp bad} {
+ if {![file isdirectory [set ${p}D [file join $options(dataDirectory) \
+ ${p}addrs]]]} {
+ file mkdir [set ${p}D]
+ }
+
+ set ${p}F [file join [set ${p}D] [join [split $origAddress /] _]]
+ }
+
+ pruneDir $tmpD addr
+
+
+# deal with Klez-inspired nonsense
+ if {([info exists options(dropNames)]) && ([catch {
+ foreach part [mime::getproperty $mime parts] {
+ catch { unset params }
+ array set params [mime::getproperty $part params]
+ if {[info exists params(name)]} {
+ foreach name $options(dropNames) {
+ if {[string match $name $params(name)]} {
+ tclLog "rejecting: $origProper <$messageID> ($subject) $params(name)"
+ cleanup
+ }
+ }
+ }
+ }
+ } result])} {
+ tclLog "Klez-check: $result"
+ }
+
+ set friend ""
+ if {[adminP $origLocal $origDomain]} {
+ tclLog "DEBUG admin check: $origProper <$messageID> ($subject)"
+
+# if DSNs were the rule, it would make sense to parse it... no such luck
+
+ set fd [open $messageFile { RDONLY }]
+ set text [read $fd]
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+
+ foreach file [glob -nocomplain [file join $badD *]] {
+ set addr [file tail $file]
+ if {([string match *$addr* $text]) \
+ || (([set x [string first @ $addr]] > 0) \
+ && ([string match \
+ *[string range $addr 0 [expr {$x-1}]]* \
+ $text]))} {
+ tclLog "failure notice: $origProper ($addr)"
+
+ cleanup
+ }
+ }
+
+ tclLog "DEBUG admin continue: $origProper <$messageID> ($subject)"
+ } elseif {(![ownerP $origLocal $origDomain]) \
+ && (![friendP $origLocal $origDomain]) \
+ && (![file exists $outF]) \
+ && (![file exists $tmpF]) \
+ && (![string compare ""\
+ [set friend [anyfriend $outD \
+ [alladdrs $mime {To cc}]]]]) \
+ && (![findPhrase $subject]) \
+ && ([info exists options(noticeFile)])} {
+ if {[file exists $badF]} {
+ catch { file delete -- $badF }
+ } elseif {[catch {
+ set fd [open $options(noticeFile) { RDONLY }]
+ set text [read $fd]
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+
+ regsub -all %passPhrase% $text [makePhrase] text
+ for {set rsubject $subject} \
+ {[regexp -nocase ^re: $rsubject]} \
+ {set rsubject [string trimleft \
+ [string range $rsubject 3 end]]} {
+ }
+ regsub -all %subject% $text $rsubject text
+
+ smtp::sendmessage \
+ [mime::initialize \
+ -canonical multipart/mixed \
+ -parts [list [mime::initialize \
+ -canonical text/plain \
+ -param {charset us-ascii} \
+ -string $text] \
+ [mime::initialize \
+ -canonical message/rfc822 \
+ -parts [list $mime]]]] \
+ -originator "" \
+ -header [list From $options(myMailbox)] \
+ -header [list To $origProper] \
+ -header [list Subject "Re: $rsubject"]
+
+ set fd [open $badF { WRONLY CREAT TRUNC }]
+ } result]} {
+ tclLog $result
+ } else {
+ catch { puts $fd $origProper }
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+ }
+ tclLog "rejecting: $origProper <$messageID> ($subject)"
+
+ cleanup
+ } elseif {[string compare $friend ""]} {
+ tclLog "accepting: $origProper because of $friend"
+ } else {
+ if {[ownerP $origLocal $origDomain]} {
+ set addrD $outD
+ } else {
+ set addrD $tmpD
+ }
+
+ foreach addr [alladdrs $mime \
+ {From To cc Resent-From Resent-To Resent-cc}] {
+ catch { unset aprops }
+
+ array set aprops $addr
+ set addrLocal $aprops(local)
+ set addrDomain $aprops(domain)
+
+ if {[string compare \
+ [set addrAddress \
+ [string tolower $addrLocal@$addrDomain]] @]} {
+ set addrF [file join $addrD [join [split $addrAddress /] _]]
+
+ if {[file exists $addrF]} {
+ continue
+ }
+
+ if {[catch { set fd [open $addrF { WRONLY CREAT TRUNC }] } \
+ result]} {
+ tclLog $result
+ } else {
+ catch { puts $fd $aprops(proper) }
+ if {[catch { close $fd } result]} {
+ tclLog $result
+ }
+ }
+ }
+ }
+ }
+
+
+# perform final actions, if we're the originator
+
+ if {[ownerP $origLocal $origDomain]} {
+ if {[info exists options(auditOutFile)]} {
+ saveMessage $messageFile $options(auditOutFile)
+ }
+
+ cleanup
+ }
+
+
+# send a copy to the pda
+
+ if {([info exists options(pdaMailboxes)]) \
+ && ([string compare [set text [mutl::gathertext $mime]] ""])} {
+ if {[info exists options(pdaMailsize)]} {
+ set text [string range $text 0 [expr {$options(pdaMailsize)-1}]]
+ }
+ set pda [mime::initialize \
+ -canonical text/plain \
+ -param {charset us-ascii} \
+ -string $text]
+
+ foreach key {From To cc Subject Date Reply-To} {
+ foreach value [mutl::getheader $mime $key] {
+ mime::setheader $pda $key $value -mode append
+ }
+ }
+
+ if {[catch { smtp::sendmessage $pda \
+ -atleastone true \
+ -originator "" \
+ -recipients $options(pdaMailboxes) } result]} {
+ tclLog $result
+ }
+ }
+
+
+# send a copy to the remote mailbox
+
+ if {[info exists options(remoteMailboxes)]} {
+ if {[catch { smtp::sendmessage $mime \
+ -atleastone true \
+ -originator "" \
+ -recipients $options(remoteMailboxes) } result]} {
+ tclLog $result
+ } else {
+ cleanup
+ }
+ }
+
+ saveMessage $messageFile
+
+
+ cleanup
+
+
+} result]} {
+ global errorCode errorInfo
+
+ set ecode $errorCode
+ set einfo $errorInfo
+
+ if {(![catch { info body tclLog } result2]) \
+ && ([string compare [string trim $result2] \
+ {catch {puts stderr $string}}])} {
+ catch { tclLog $result }
+ }
+
+ catch {
+ smtp::sendmessage \
+ [mime::initialize \
+ -canonical text/plain \
+ -param {charset us-ascii} \
+ -string "$result\n\nerrorCode: $ecode\n\n$einfo"] \
+ -originator "" \
+ -header [list From [id user]@[info hostname]] \
+ -header [list To operator@[info hostname]] \
+ -header [list Subject "[info hostname] fatal $program"]
+ }
+
+ cleanup $result
+}
+
+
+exit 75
diff --git a/tcllib/examples/mime/mbot/pkgIndex.tcl b/tcllib/examples/mime/mbot/pkgIndex.tcl
new file mode 100644
index 0000000..29acd62
--- /dev/null
+++ b/tcllib/examples/mime/mbot/pkgIndex.tcl
@@ -0,0 +1,3 @@
+# @mdgen EXCLUDE: impersonal.tcl
+package ifneeded mutl 1.0 [list source [file join $dir mutl.tcl]]
+package ifneeded mbox 1.0 [list source [file join $dir mbox.tcl]]