chiark / gitweb /
Initial commit as found
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 9 Nov 2009 18:16:08 +0000 (18:16 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 9 Nov 2009 18:16:08 +0000 (18:16 +0000)
106 files changed:
.gitignore [new file with mode: 0644]
crontab [new file with mode: 0644]
inews [new file with mode: 0755]
inews.test [new file with mode: 0755]
stump/LICENSE [new file with mode: 0644]
stump/README [new file with mode: 0644]
stump/bin/acceptFromMod.pl [new file with mode: 0755]
stump/bin/activity [new file with mode: 0755]
stump/bin/addKey [new file with mode: 0755]
stump/bin/antivirus [new file with mode: 0755]
stump/bin/bogusMessage [new file with mode: 0755]
stump/bin/buildNewsgroupsDB [new file with mode: 0755]
stump/bin/checkquot [new file with mode: 0755]
stump/bin/createArchive [new file with mode: 0755]
stump/bin/cuthead [new file with mode: 0755]
stump/bin/decodeBase64 [new file with mode: 0755]
stump/bin/email-server.pl [new file with mode: 0755]
stump/bin/isbinary [new file with mode: 0755]
stump/bin/listKeys [new file with mode: 0755]
stump/bin/logArt [new file with mode: 0755]
stump/bin/needAck [new file with mode: 0755]
stump/bin/noAck [new file with mode: 0755]
stump/bin/preApprove [new file with mode: 0755]
stump/bin/processApproved [new file with mode: 0755]
stump/bin/processNoack.pl [new file with mode: 0755]
stump/bin/processPreapproved [new file with mode: 0755]
stump/bin/processRejected [new file with mode: 0755]
stump/bin/report.sh [new file with mode: 0755]
stump/bin/robomod.pl [new file with mode: 0755]
stump/bin/send_pgp_key [new file with mode: 0755]
stump/bin/stump-pgp [new file with mode: 0755]
stump/bin/stump-report.pl [new file with mode: 0755]
stump/bin/stump.pl [new file with mode: 0755]
stump/bin/submission.pl [new file with mode: 0755]
stump/bin/submitFailed [new file with mode: 0755]
stump/bin/suspicious.pl [new file with mode: 0755]
stump/bin/verifySignature [new file with mode: 0755]
stump/c/antivirus.c [new file with mode: 0644]
stump/c/checkquot.c [new file with mode: 0644]
stump/c/compile [new file with mode: 0755]
stump/c/cuthead.c [new file with mode: 0644]
stump/c/isbinary.c [new file with mode: 0644]
stump/doc/README [new file with mode: 0644]
stump/doc/VERSION [new file with mode: 0644]
stump/etc/README [new file with mode: 0644]
stump/etc/my_rnews [new file with mode: 0755]
stump/etc/procmailrc [new file with mode: 0644]
stump/local/README [new file with mode: 0644]
stump/local/bin/pmapp [new file with mode: 0755]
stump/local/bin/pmcanon [new file with mode: 0755]
stump/local/bin/pmcheck [new file with mode: 0755]
stump/local/bin/pmnewsgroups [new file with mode: 0755]
stump/local/doc/PGPMoose.README [new file with mode: 0644]
stump/local/man/man1/pmapp.1 [new file with mode: 0644]
stump/local/man/man1/pmcanon.1 [new file with mode: 0644]
stump/local/man/man1/pmcheck.1 [new file with mode: 0644]
stump/local/man/man1/pmnewsgroups.1 [new file with mode: 0644]
stump/local/src/PGPMoose.shar.gz [new file with mode: 0644]
webstump/LICENSE [new file with mode: 0644]
webstump/Makefile [new file with mode: 0644]
webstump/README [new file with mode: 0644]
webstump/TODO [new file with mode: 0644]
webstump/config/convert-newsgroups.sh [new file with mode: 0755]
webstump/config/motd [new file with mode: 0644]
webstump/demo/error.html [new file with mode: 0644]
webstump/demo/login.html [new file with mode: 0644]
webstump/demo/main-screen.html [new file with mode: 0644]
webstump/demo/password.html [new file with mode: 0644]
webstump/demo/review.html [new file with mode: 0644]
webstump/demo/welcome.html [new file with mode: 0644]
webstump/doc/help/bad.posters.list.html [new file with mode: 0644]
webstump/doc/help/bad.subjects.list.html [new file with mode: 0644]
webstump/doc/help/bad.words.list.html [new file with mode: 0644]
webstump/doc/help/filter-lists.html [new file with mode: 0644]
webstump/doc/help/good.posters.list.html [new file with mode: 0644]
webstump/doc/help/good.subjects.list.html [new file with mode: 0644]
webstump/doc/help/watch.posters.list.html [new file with mode: 0644]
webstump/doc/help/watch.subjects.list.html [new file with mode: 0644]
webstump/doc/help/watch.words.list.html [new file with mode: 0644]
webstump/images/bg1.jpg [new file with mode: 0644]
webstump/images/construction.gif [new file with mode: 0644]
webstump/images/help.gif [new file with mode: 0644]
webstump/images/new_tiny2.gif [new file with mode: 0644]
webstump/images/no_image.gif [new file with mode: 0644]
webstump/images/smiley.gif [new file with mode: 0644]
webstump/images/star.gif [new file with mode: 0644]
webstump/images/warning_big.gif [new file with mode: 0644]
webstump/index.html [new file with mode: 0644]
webstump/scripts/create-newsgroup.pl [new file with mode: 0755]
webstump/scripts/file-message.pl [new file with mode: 0755]
webstump/scripts/filter.lib.pl [new file with mode: 0644]
webstump/scripts/gatekeeper-file-message.pl [new file with mode: 0755]
webstump/scripts/gatekeeper.lib [new file with mode: 0644]
webstump/scripts/html_output.pl [new file with mode: 0644]
webstump/scripts/mime-parsing.lib [new file with mode: 0644]
webstump/scripts/strip-ats.pl [new file with mode: 0755]
webstump/scripts/webstump.lib.pl [new file with mode: 0644]
webstump/scripts/webstump.pl [new file with mode: 0755]
webstump/src/Makefile [new file with mode: 0644]
webstump/src/wrapper.c [new file with mode: 0644]
webstump/test/mm-test.pl [new file with mode: 0755]
webstump/tmp/demo.happy99.txt [new file with mode: 0644]
webstump/tmp/demo.mime.txt [new file with mode: 0644]
webstump/tmp/demo.text.txt [new file with mode: 0644]
xlog/bin/record [new file with mode: 0755]
xlog/bin/report [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0299241
--- /dev/null
@@ -0,0 +1,14 @@
+*~
+/archive
+/errs
+/errs.[0-9]
+/errs.[0-9].gz
+/stump/data
+/stump/tmp
+/webstump/bin/q
+/webstump/bin/wrapper
+/webstump/config/*
+/webstump/queues/*/dir_*
+/xlog/log/*/event.log
+/xlog/log/*/event.log.[0-9]
+/xlog/log/*/event.log.[0-9].gz
diff --git a/crontab b/crontab
new file mode 100644 (file)
index 0000000..602ab06
--- /dev/null
+++ b/crontab
@@ -0,0 +1,7 @@
+#
+# install with
+#  ssh webstump@chiark crontab live/crontab
+#
+#m h  d m dow
+50 7  12 * *   savelog live/xlog/log/uk.rec.cycling.moderated/event.log
+50 7  12 * *   savelog live/errs
diff --git a/inews b/inews
new file mode 100755 (executable)
index 0000000..6ee5968
--- /dev/null
+++ b/inews
@@ -0,0 +1,43 @@
+#!/bin/sh
+set -e
+
+#real=false
+real=true
+
+export NNTPSERVER=nnrp.chiark.greenend.org.uk
+export NNTPAUTH='md5cookie1way chiark'
+
+trap 'rm -f "$tf"' 0
+
+tf=`mktemp`
+
+sed '${ /^$/d }' >$tf
+
+set +e
+output=`
+       set -e
+       exec 2>&1
+       if $real; then
+               inews -h -Q -R <$tf
+       else
+               (set -e
+                echo "Newsgroups: chiark.test.moderated"
+                sed 's/^Newsgroups:/X-Would-Newsgroups:/' $tf) | inews -h
+       fi
+`
+rc=$?
+set -e
+
+if [ $rc = 0 ]
+then
+       echo 'posted ok!'
+       $HOME/live/xlog/bin/record posted uk.rec.cycling.moderated <$tf
+       exit 0
+fi
+
+(
+       printf "Errors: %s" "$output"
+       echo
+       echo ======================
+       cat $tf
+) | mail -s 'lost moderated newsgroup submission' webstump
diff --git a/inews.test b/inews.test
new file mode 100755 (executable)
index 0000000..6ee5968
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+set -e
+
+#real=false
+real=true
+
+export NNTPSERVER=nnrp.chiark.greenend.org.uk
+export NNTPAUTH='md5cookie1way chiark'
+
+trap 'rm -f "$tf"' 0
+
+tf=`mktemp`
+
+sed '${ /^$/d }' >$tf
+
+set +e
+output=`
+       set -e
+       exec 2>&1
+       if $real; then
+               inews -h -Q -R <$tf
+       else
+               (set -e
+                echo "Newsgroups: chiark.test.moderated"
+                sed 's/^Newsgroups:/X-Would-Newsgroups:/' $tf) | inews -h
+       fi
+`
+rc=$?
+set -e
+
+if [ $rc = 0 ]
+then
+       echo 'posted ok!'
+       $HOME/live/xlog/bin/record posted uk.rec.cycling.moderated <$tf
+       exit 0
+fi
+
+(
+       printf "Errors: %s" "$output"
+       echo
+       echo ======================
+       cat $tf
+) | mail -s 'lost moderated newsgroup submission' webstump
diff --git a/stump/LICENSE b/stump/LICENSE
new file mode 100644 (file)
index 0000000..5107f64
--- /dev/null
@@ -0,0 +1,355 @@
+What this legalese means is basically two things:
+
+1) There is NO WARRANTY
+2) You are free to copy and modify this program as long as you do 
+not impose more restrictive terms on its copying.
+
+igor
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.  59 Temple Place -
+Suite 330, Boston, MA  02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom
+to share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and
+to any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Library General Public
+License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it in
+new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And
+you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on,
+we want its recipients to know that what they have is not the original,
+so that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it, either
+verbatim or with modifications and/or translated into another language.
+(Hereinafter, translation is included without limitation in the term
+"modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of running
+the Program is not restricted, and the output from the Program is covered
+only if its contents constitute a work based on the Program (independent
+of having been made by running the Program). Whether that is true depends
+on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously
+and appropriately publish on each copy an appropriate copyright notice
+and disclaimer of warranty; keep intact all the notices that refer to
+this License and to the absence of any warranty; and give any other
+recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of
+it, thus forming a work based on the Program, and copy and distribute
+such modifications or work under the terms of Section 1 above, provided
+that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any part
+    thereof, to be licensed as a whole at no charge to all third parties
+    under the terms of this License.
+
+    c) If the modified program normally reads commands interactively when
+    run, you must cause it, when started running for such interactive
+    use in the most ordinary way, to print or display an announcement
+    including an appropriate copyright notice and a notice that there
+    is no warranty (or else, saying that you provide a warranty) and
+    that users may redistribute the program under these conditions, and
+    telling the user how to view a copy of this License. (Exception: if
+    the Program itself is interactive but does not normally print such
+    an announcement, your work based on the Program is not required to
+    print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be
+reasonably considered independent and separate works in themselves,
+then this License, and its terms, do not apply to those sections when
+you distribute them as separate works. But when you distribute the same
+sections as part of a whole which is a work based on the Program, the
+distribution of the whole must be on the terms of this License, whose
+permissions for other licensees extend to the entire whole, and thus to
+each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works
+based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the
+scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections 1
+    and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years,
+    to give any third party, for a charge no more than your cost of
+    physically performing source distribution, a complete machine-readable
+    copy of the corresponding source code, to be distributed under the
+    terms of Sections 1 and 2 above on a medium customarily used for
+    software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code. (This alternative is allowed
+    only for noncommercial distribution and only if you received the
+    program in object code or executable form with such an offer, in
+    accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the executable. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major components
+(compiler, kernel, and so on) of the operating system on which the
+executable runs, unless that component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access
+to copy from a designated place, then offering equivalent access to
+copy the source code from the same place counts as distribution of the
+source code, even though third parties are not compelled to copy the
+source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and will
+automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will
+not have their licenses terminated so long as such parties remain in
+full compliance.
+
+5. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute
+the Program or its derivative works. These actions are prohibited
+by law if you do not accept this License. Therefore, by modifying or
+distributing the Program (or any work based on the Program), you indicate
+your acceptance of this License to do so, and all its terms and conditions
+for copying, distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further restrictions
+on the recipients' exercise of the rights granted herein. You are not
+responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute
+so as to satisfy simultaneously your obligations under this License
+and any other pertinent obligations, then as a consequence you may not
+distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who
+receive copies directly or indirectly through you, then the only way you
+could satisfy both it and this License would be to refrain entirely from
+distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any such
+claims; this section has the sole purpose of protecting the integrity of
+the free software distribution system, which is implemented by public
+license practices. Many people have made generous contributions to the
+wide range of software distributed through that system in reliance on
+consistent application of that system; it is up to the author/donor to
+decide if he or she is willing to distribute software through any other
+system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an
+explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail
+to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a version
+number of this License, you may choose any version ever published by
+the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software
+and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
+AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING
+BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
+LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
+TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
+HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+one line to give the program's name and an idea of what it does.
+Copyright (C) 19yy  name of author
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision
+comes with ABSOLUTELY NO WARRANTY; for details type `show w'.  This is
+free software, and you are welcome to redistribute it under certain
+conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License.  Of course, the commands
+you use may be called something other than `show w' and `show c'; they
+could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the program,
+if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications
+with the library. If this is what you want to do, use the GNU Library
+General Public License instead of this License.
+
+Return to GNU's home page.
+
+FSF & GNU inquiries & questions to gnu@gnu.org. Other ways to contact
+the FSF.
+
+Comments on these web pages to webmasters@www.gnu.org, send other
+questions to gnu@gnu.org.
+
+Copyright notice above.  Free Software Foundation, Inc., 59 Temple Place -
+Suite 330, Boston, MA 02111, USA
+
+Updated: 16 Feb 1998 tower
+
diff --git a/stump/README b/stump/README
new file mode 100644 (file)
index 0000000..756d768
--- /dev/null
@@ -0,0 +1,25 @@
+STUMP is a free robomoderator program for moderators of USENET
+newsgroups and mailing lists. 
+
+STUMP is covered by the GNU Public License (see file LICENSE). This means
+that you can use this code as you see fit, however I am not liable for
+any damages whatsoever that can result in connection with your use of
+this program.
+
+For installation instructions and general information, please see
+
+       http://www.algebra.com/~ichudov/stump/
+
+Note that even though STUMP is free, for those without Unix background 
+or a Unix account I provide commercial moderation bot hosting. See the
+webpage for details.
+
+If you have installation questions (and even if you do not), please
+join the stump-users mailing list. To subscribe, send email to 
+majordomo@algebra.com, saying (in the message body)
+
+subscribe stump-users
+
+Send your messages to the mailing list to stump-users@algebra.com. I
+monitor this list and try to answer questions. Along with your questions,
+please tell us the name of your newsgroup.
diff --git a/stump/bin/acceptFromMod.pl b/stump/bin/acceptFromMod.pl
new file mode 100755 (executable)
index 0000000..9927f5c
--- /dev/null
@@ -0,0 +1,155 @@
+#
+# This script accepts an email from moderators and processes
+# an approval or a rejection.
+#
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "\$MNG_ROOT is not defined";
+$Prefix = $ENV{'BOT_SUBJECT_PREFIX'} 
+  || die "\$BOT_SUBJECT_PREFIX is not defined";
+$MessageNumber = "";
+$Error = "";
+#######################################################################
+# notifies the bot supporter
+#
+sub processError {
+  $msg = pop( @_ );
+
+print STDERR "Approval error: " . $msg . "\n"; 
+
+  if( $MessageFile && -r $MessageFile ) {
+    system( "suspicious no NOTICE: This message is being RE-SENT to you because of the following error in your approval: $msg < $MessageFile" );
+  } else {
+    print STDERR "ERROR: Completely bogus approval from $From, $msg\n";
+  }
+
+  exit 1;
+}
+####################################################################### main
+# handling headers
+#
+while( <STDIN> ) {
+  chop;
+
+  if( /^$/ ) {
+    goto after_header;
+  }
+  if( /^Subject: /i  && !$Subject ) {
+    $Subject = $_;
+    $Subject =~ s/^Subject: //i;
+    if( /::$Prefix\// ) {
+      $MessageNumber = $Subject;
+      $MessageNumber =~ s/^.*::$Prefix\///;
+      $MessageNumber =~ /(\d+)/;
+      $MessageNumber = $1;
+
+      $MessageFile = "$MNG_ROOT/tmp/messages/$MessageNumber";
+      if( !($MessageNumber =~ /[0-9]+/) || !(-r $MessageFile) )
+        {
+          $Error = "Message number in subject incorrect";
+        }
+    } else {
+      $Error = "no message number";
+    }
+  } elsif ( /^From: /i ) {
+    $From = $_;
+    $From =~ s/^From: //i;
+  }
+}
+
+
+after_header:
+&processError($Error) if( $Error );
+#
+# Now we are looking at the body
+#
+$done = "";
+$command = "";
+$comment = "";
+while( <STDIN> ) {
+  chop;
+  if( /preapprove/i ) {
+    $command = "processPreapproved xxx";
+    $done = "yes";
+    goto after_body;
+  }
+  if( /approve/i ) {
+      $command = "processApproved xxx ";
+    $done = "yes";
+    goto after_body;
+  }
+  if( /reject/i ) {
+    $reason = $_;
+    $reason =~ s/^.*reject //i;
+    $reason =~ s/( |`|'|"|;|\.|\/|\\)//g;
+    &processError( "wrong rejection reason" ) if( !$reason );
+
+    &processError( "Wrong rejection reason" )
+       if( !( -r "$MNG_ROOT/etc/messages/$reason" ) 
+          && ($reason ne "custom")
+         );
+    $command = "processRejected xxx $reason";
+    $done = "yes";
+    goto after_body;
+  }
+}
+
+after_body:
+print STDERR "After body\n";
+
+&processError( "No Command Specified" ) if( !$command );
+
+while( <> ) {
+  if( /^comment/ ) {
+    s/^comment//;
+    $comment = $_;  
+    $comment .= $_ while( <> );
+  }
+}
+
+print STDERR "Comment is: $comment\n" if( $comment );
+
+$ENV{'EXPLANATION'} = $comment;
+
+open( COMMAND, "| $command" ) ||  &processError( "$command failed" );
+
+  open( MESSAGE, "$MessageFile" ) || &processError( "Can't open $MessageFile" );
+  print COMMAND while( <MESSAGE> );
+  close( MESSAGE );
+
+#  if( $comment && !($command =~ '^processRejected') ) {
+#    print COMMAND 
+#       "\n======================================= MODERATOR'S COMMENT: \n" .
+#       $comment;
+#  }
+#close( COMMAND );
+
+&processError( "No action specified" ) 
+  if( $done ne "yes" );
+
+unlink( $MessageFile );
+
+
+1;
diff --git a/stump/bin/activity b/stump/bin/activity
new file mode 100755 (executable)
index 0000000..f8d008e
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+grep '^Activity' < $HOME/Mail/from | less
diff --git a/stump/bin/addKey b/stump/bin/addKey
new file mode 100755 (executable)
index 0000000..69da919
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# $Id: addKey,v 1.2 2007/05/03 23:47:11 rram Exp $
+# Modified to work with GPG
+
+PUBRING=$MNG_ROOT/data/pubring.gpg
+
+gpg --import --armor --no-default-keyring --keyring $PUBRING
diff --git a/stump/bin/antivirus b/stump/bin/antivirus
new file mode 100755 (executable)
index 0000000..a513c35
Binary files /dev/null and b/stump/bin/antivirus differ
diff --git a/stump/bin/bogusMessage b/stump/bin/bogusMessage
new file mode 100755 (executable)
index 0000000..e3a9d7b
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+echo Bogus message: $@ 1>&2
+
+cat | mail -s "Bogus Message" $ADMIN
diff --git a/stump/bin/buildNewsgroupsDB b/stump/bin/buildNewsgroupsDB
new file mode 100755 (executable)
index 0000000..d8b5323
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+
+$DataDir = $ENV{'MNG_ROOT'} . "/data";
+$NewsgroupsDBFile = "$DataDir/Newsgroups";
+
+# Open message-ID database
+dbmopen( %NewsgroupsDB, "$NewsgroupsDBFile", 0644 );
+
+
+while( <> ) {
+  ($group, $q1, $q2, $flag) = split;
+  $NewsgroupsDB{$group} = $flag;
+}
+
+print "test: $NewsgroupsDB{'soc.culture.russian'}.\n"
diff --git a/stump/bin/checkquot b/stump/bin/checkquot
new file mode 100755 (executable)
index 0000000..dcda917
Binary files /dev/null and b/stump/bin/checkquot differ
diff --git a/stump/bin/createArchive b/stump/bin/createArchive
new file mode 100755 (executable)
index 0000000..13fde59
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+DATE="`date +%y%m%d`"
+
+ArchFile() {
+  FILE=$1
+  DEST=$MNG_ROOT/archive/old/$FILE.$DATE
+  mv $MNG_ROOT/archive/$FILE $DEST
+  gzip -9 $MNG_ROOT/archive/old/$FILE.$DATE
+  chmod 644 $DEST.gz
+  ln -fs $DEST.gz $MNG_ROOT/archive/old/$FILE.current
+}
+
+ArchFile approved
+ArchFile rejected
+ArchFile incoming
+
+echo New copy of archive has been created. Please update your home page \
+  | mail $ADMIN
diff --git a/stump/bin/cuthead b/stump/bin/cuthead
new file mode 100755 (executable)
index 0000000..5469dfc
Binary files /dev/null and b/stump/bin/cuthead differ
diff --git a/stump/bin/decodeBase64 b/stump/bin/decodeBase64
new file mode 100755 (executable)
index 0000000..601c62b
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+
+$isEncoded = 0;
+$FullHeaders = "";
+$DecodedHeaders = "";
+$switch = "";
+
+while(<>) {
+  chop;
+
+  last if( /^$/ );
+
+  $FullHeaders .= "$_\n";
+
+  if( /^Content-Transfer-Encoding: BASE64/i ) {
+
+    $isEncoded = 1;
+    $switch = "-b"; # base64 - default
+
+  } elsif( /^Content-Transfer-Encoding: quoted-printable/i ) {
+
+    $isEncoded = 1;
+    $switch = "-q"; # quoted-printable
+
+  } elsif( ! /^Mime-Version: /i && ! /Content-Type: /i ) {
+    $DecodedHeaders .= "$_\n";
+  }
+}
+
+# body
+
+if( !$isEncoded ) {
+  print $FullHeaders . "\n";
+  while( <> ) {
+    print;
+  }
+  exit 0;
+}
+
+print $DecodedHeaders . "\n";
+
+if( $ENV{'TMP'} ) {
+  $TmpFile = "$ENV{'TMP'}/letter.$$";
+} else {
+  $TmpFile = "/tmp/letter.$$";
+}
+
+open( DECODE, "|mimencode -u $switch > $TmpFile" ) 
+  || die "Can't run mmencode writing to $TmpFile";
+
+  while( <> ) {
+    print DECODE;
+  }
+close( DECODE );
+
+open( DECODED, $TmpFile ) || die "Can't open just decoded file $TmpFile";
+  while( <DECODED> ) {
+    print;
+  }
+close( DECODED );
+
+unlink( $TmpFile );
diff --git a/stump/bin/email-server.pl b/stump/bin/email-server.pl
new file mode 100755 (executable)
index 0000000..c54de15
--- /dev/null
@@ -0,0 +1,413 @@
+#!/usr/bin/perl
+######################################################################
+# This perl script is a file server. It allows users
+# to retrieve and alter certain files.
+# Its purpose is to allow them to manage their installations
+# without the need to log in remotely and without the need to know 
+# anything about Unix(tm).
+#
+# It only allows retrieving and updating files that have been
+# explicitly mentioned as available in its configuration file.
+#
+# USAGE: 
+#
+# First, you have to come up with a password (I strongly suggest using 
+# a password that is DIFFERENT from your regular Unix password).
+#
+# Save it in file $HOME/.email-server.pwd
+#
+# Second, create a file $HOME/.email-server.cfg and list files (and their
+# optional text descriptions) like this:
+#
+# stump-faq tmp/stump-users-faq.txt FAQ for STUMP users
+# rw:mydir some/dir/mydir Directory with test files
+#
+# The syntax of this file is as follows:
+#
+# [mode:]filehandle fullpath comment
+#
+# where mode is either r or rw. r means read only (which is the 
+# default mode that is used if no mode is mentioned], and rw means that 
+# the file can be both read and changed. 
+#
+# filehandle is the "external" name by 
+# which the file will be known to the email user. It may be different from 
+# the actual file name.
+#
+# fullpath is the actual path to the file or directory that you want
+# to make available, RELATIVE to your home directory.
+#
+# This script is supposed to be called from your .procmailrc. To recognize
+# your requests to this script from other emails, perhaps you could send
+# them with a different To: address, for example you could always use
+#
+#      To: your@email.address (Email File Server)
+#
+# and then use a procmail recipe like
+#
+# :0:
+# * ^To: .*Email File Server
+# | email-server.pl
+#
+# Copyright(C) 1998 Igor Chudov, ichudov@algebra.com, 
+#
+#               http://www.algebra.com/~ichudov.
+#
+# GNU Public License applies.
+# There is NO WARRANTY WHATSOEVER. USE THIS PROGRAM AT YOUR OWN RISK.
+#
+######################################################################
+
+sub read_config {
+  my $config_file = "$SERVER_ROOT/.email-server.cfg";
+  open( CONFIG, $config_file )
+       || die "Config file $config_file not found.";
+  while( <CONFIG> ) {
+    chop;
+    if( ! /^#/ ) {
+      my ($mode_handle, $file, @explanation) = split;
+
+      my $mode = "r", $handle = $mode_handle; # if no mode is present that's 
+                                              # the default
+
+      if( $mode_handle =~ /:/ ) {
+        ($mode, $handle) = split( /:/, $mode_handle );
+      }
+
+      print STDERR "File $file served by email server does not exist in $SERVER_ROOT\n"
+        if( ! -e "$SERVER_ROOT/$file" );
+
+      print STDERR "Mode must be rw or r" 
+        if( $mode ne "r" && $mode ne "rw" );
+
+      $served_files{$handle} = $file;
+      $explanations{$handle} = join( " ", @explanation );
+      $file_modes{$handle} = $mode;
+    }
+  }
+  close( CONFIG );
+}
+
+sub init {
+
+  die "HOME is not defined!!!"
+    if not defined $ENV{'HOME'};
+
+  $SERVER_ROOT = $ENV{'HOME'};
+
+  &read_config;
+  open( PASSWORD, "$SERVER_ROOT/.email-server.pwd" );
+  $password = <PASSWORD>;
+  chop $password if( $password =~ /\n$/ );
+  close( PASSWORD );
+
+  if( defined $ENV{'EMAIL_SERVER_ADDRESS'} ) {
+    $email_server_address = $ENV{'EMAIL_SERVER_ADDRESS'};
+  } else {
+    $email_server_address = "bad_address\@stump.algebra.com (WRONG ADDRESS)";
+  }
+
+  my @sendmail_dirs = ("/bin", "/usr/bin", "/usr/sbin", "/usr/lib" );
+
+  $sendmail = "/bin/mail"; # last resort...
+
+  foreach (@sendmail_dirs) {
+    $sendmail = "$_/sendmail"
+      if( -x "$_/sendmail" );
+  }
+
+  $short_help = "\nSend email with 'help' in subject field to receive an\n" .
+                "explanation on how to work with this email server.\n";
+  $long_help = "
+
+Hi there, 
+
+Thanks for asking for help! Here it is.
+
+I am an automated email server. I am here to help you manage your files
+remotely without the need to log in and use Unix commands to edit your
+configuration files.
+
+You can retrieve this help message by sending me an email with the only
+word 'help' in the Subject: field of your message.
+
+I process your commands. I do a limited set of simple tasks, such as
+retrieving and modifying text configuration files. Only certain files
+can be retrieved and modified; this is done for your own security.
+
+Whenever you retrieve a file or want to modify a file, you will have
+to provide a password to me, as well as the command that I will execute.
+Passwords are used to ensure that only authorized users can perform
+important operations; however, anyone can request and receive this
+help message.
+
+IF YOU DO NOT KNOW THE PASSWORD, you have to ask the 
+administrator of my account to provide you with one.
+
+All commands, along with passwords, should be specified in the Subject: 
+field of your messages. A password always goes first, followed by the
+command. For example, if your password is 'xyzzy' and the command is
+'get moderators' (more on commands later), then the Subject: field 
+should be
+
+       Subject: xyzzy: get moderators
+
+Both commands and passwords are NOT case sensitive. You can mix uppercase
+with lowercase as you wish.
+
+COMMANDS: 
+
+Right now, there are three kinds of commands:
+
+1. help. This command requests help. Requires no password.
+
+2. get filename. This command requests a file to be sent from the 
+   server to you. The body of your message is ignored. 
+
+   Example (assuming your password is xyzzy):
+
+   Subject: xyzzy: get bad.guys.list
+
+   Note that for certain files, their \"names\" may consist of
+   directory name, followed by a \"/\" (slash) character and the name
+   of the file. For example, a get command may be of form:
+
+   Subject: xyzzy: get messages/offtopic
+
+3. set filename. This command requests that the contents of the file be set
+   to the text in the body of your message. 
+   Example (assuming your password is xyzzy):
+   Subject: xyzzy: set bad.guys.list
+
+   spammer\@cyberpromo.com
+   flamer\@netcom.com
+   <END OF MESSAGE>
+
+   Note that for certain files, their \"names\" may consist of
+   directory name, followed by a \"/\" (slash) character and the name
+   of the file. For example, a set command may be of form:
+
+   Subject: xyzzy: set messages/offtopic
+
+   Thanks for submitting your article to comp.sys.foobars.moderated. Your
+   article is offtopic and is being rejected. Have a nice day!
+
+FILES THAT CAN BE RETRIEVED AND CHANGED.
+
+The following are the file names (that you can mention in set and get 
+commands) that are supported by this installation:
+
+NAME OF FILE               Explanation
+
+";
+
+  foreach( keys %served_files ) {
+    $long_help .= $_ . substr( "                    ", length( $_ ), 100 );
+
+    my $mode = $file_modes{$_};
+    if( $mode eq "r" ) { $long_help .= "(Read-Only) "; }
+    else { $long_help .= "(Read-Write) "; }
+
+    $long_help .= $explanations{$_} . "\n";
+
+    my $file = "$SERVER_ROOT/$served_files{$_}";
+
+    if( ! -e $file ) {
+      $long_help .= "(this file does NOT exist)\n";
+    } elsif( -d $file ) {
+      $long_help .= "$_ is a DIRECTORY. Available files are: \n";
+      opendir( DIR, $file );
+      my $dir = $_;
+      my $fn;
+      while( $fn = readdir( DIR ) ) {
+        my $file1 = "$file/$fn";
+        if( ! /^\./  && -f $file1 && -r $file1 ) {
+          $long_help .= "\t$dir/$fn\n";
+        }
+      }
+      closedir( DIR );
+    }
+  }
+}
+
+sub reply {
+  my $msg = pop( @_ );
+
+  my $address = $From;
+  $address =~ s/^From: //i;
+
+  if( defined $ReplyTo ) {
+    $address = $ReplyTo;
+    $address =~ s/^Reply-To: //i;
+    $address =~ s/`//g;
+    $address =~ s/;//g;
+  }
+
+  open( SENDMAIL, "|$sendmail '$address'" ) 
+       || die "Could not start sendmail in $sendmail";
+
+  print SENDMAIL "From: $To\n";
+  print SENDMAIL "To: $address\n";
+  print SENDMAIL "Subject: Re: $Subject\n";
+
+  print SENDMAIL "\n";
+
+  print SENDMAIL "$msg\n";
+
+  close( SENDMAIL );
+
+}
+
+sub user_error {
+  my ($msg) = pop( @_ );
+  &reply( "You made a mistake:\n\n$msg\n$short_help\n" .
+          "Message Follows:\n\n$Headers\n$Body\n" );
+  exit 0;
+}
+
+sub readMessage {
+
+  while(<STDIN>) {
+    s/^From />From /;
+    last if( /^$/ );
+    $Headers .= $_;
+    chop;
+    if( /^Subject: / ) {
+      $Subject = $_;
+      $Subject =~ s/^Subject: //;
+      $Subject = "\L$Subject";
+    } elsif( /^From: / ) {
+      $From = $_;
+      $From =~ s/^From: //;
+    } elsif( /^To: / ) {
+      $To = $_;
+      $To =~ s/^To: //;
+    } elsif( /^Reply-To: / ) {
+      $ReplyTo = $_;
+      $ReplyTo =~ s/^Reply-To: //;
+    } 
+  }
+
+  while( <STDIN> ) {
+    s/^From />From /;
+    $Body .= $_;
+  }
+}
+
+sub file_from_arg {
+  my $arg = pop( @_ );
+
+  if( $arg =~ /\// ) {
+    my ($dir, $file) = split( /\//, $arg );
+    # now clean $file
+    $file =~ s/\///g;
+    $file =~ s/^\.//g;
+
+    if( defined $served_files{$dir} ) {
+      my $fullpath = "$SERVER_ROOT/$served_files{$dir}/$file";
+      return $fullpath if( -f $fullpath );
+    }
+
+  } else {
+    if( defined $served_files{$arg} ) {
+      my $fullpath = "$SERVER_ROOT/$served_files{$arg}";
+      return $fullpath if( -f $fullpath );
+    }
+  }
+}
+
+sub mode_from_arg {
+  my $arg = pop( @_ );
+  if( $arg =~ /\// ) {
+    my ($dir, $file) = split( /\//, $arg );
+    return $file_modes{$dir} if( defined $file_modes{$dir} );
+  } else {
+    return $file_modes{$arg};
+  }
+}
+
+sub command_get {
+  my $arg = pop( @_ );
+  my $file = &file_from_arg( $arg );
+
+  &user_error( "File $arg is not in the list of available files. Perhaps\n" .
+               "it is a directory or maybe you just misspelled its name." )
+    if( !$file );
+
+  if( -r $file ) {
+
+    my $reply_body = "";
+
+    open( FILE, $file );
+    $reply_body .= $_ while( <FILE> );
+    close( FILE );
+
+    &reply( $reply_body );
+
+  } else { 
+    &user_error( "File $arg does not exist or is not readable" );
+  }
+}
+
+sub command_set {
+  my $arg = pop( @_ );
+
+  my $file = &file_from_arg( $arg );
+  my $mode = &mode_from_arg( $arg );
+
+  &user_error( "File $arg is not in the list of available files." )
+    if( !$file );
+
+  if( -w $file && -f $file && $mode eq "rw" ) {
+
+    my $reply_body = "Succeeded in writing to file '$arg':\n\n$Body";
+
+    if( open( FILE, ">$file" ) ) {
+      print FILE $Body;
+      close( FILE );
+    } else {
+      $reply_body = "Failed to write to file $arg:\n\n$Body";
+    }
+
+    &reply( $reply_body );
+
+  } else { 
+    &user_error( "File $arg does not exist or is not writable" );
+  }
+}
+
+sub main {
+  &init;
+  &readMessage;
+  &user_error( "No Subject: field provided in your message" ) if( !$Subject );
+
+  if( $Subject =~ /^help/ ) {
+    &reply( $long_help );
+  } elsif( $Subject =~ /:/ ) {
+    my ($pass, $command) = split( /:/, $Subject );
+
+    &user_error("Invalid Password") if( "\L$pass" ne "\L$password" );
+
+    $command =~ s/^ +//;
+
+    my @command = split / /, $command;
+    $command = shift @command;
+    $command = "\L$command"; # lowercase
+
+    my $argument = shift @command;
+    $argument = "\L$argument";
+
+    if( $command eq "get" ) {
+      &command_get( $argument );
+    } elsif( $command eq "set" ) {
+      &command_set( $argument );
+    } else {
+      &user_error( "Invalid command: $command" );
+    }
+  }
+}
+
+######################################################################
+&main;
diff --git a/stump/bin/isbinary b/stump/bin/isbinary
new file mode 100755 (executable)
index 0000000..5fec697
Binary files /dev/null and b/stump/bin/isbinary differ
diff --git a/stump/bin/listKeys b/stump/bin/listKeys
new file mode 100755 (executable)
index 0000000..78f691c
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# $Id: listKeys,v 1.2 2007/05/03 23:47:32 rram Exp $
+# Modified to work with GPG
+
+PUBRING=$MNG_ROOT/data/pubring.gpg
+
+gpg --list-keys --no-default-keyring --keyring $PUBRING
+
diff --git a/stump/bin/logArt b/stump/bin/logArt
new file mode 100755 (executable)
index 0000000..13ddaf3
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+$MNG_ROOT=$ENV{'MNG_ROOT'};
+
+$date = `date`; chop $date;
+
+print STDERR "Activity: $date $ARGV[0] "; shift @ARGV;
+
+######################################################################
+# Getting data
+sub readMessage {
+  $IsBody = 0;
+  
+  while( <> ) {
+
+    chop;
+  
+    if( /^$/ ) {
+      $IsBody = 1;
+    }
+  
+    if( !$IsBody ) {
+  
+      if( /^Newsgroups: / ) {
+        $Newsgroups = $_;
+      } elsif( /^Subject: / ) {
+        $Subject = $_;
+      } elsif( /^From: / ) {
+        $From = $_;
+      } elsif( /^Path: / ) {
+        $Path = $_;
+      } elsif( /^Keywords: / ) {
+        $Keywords = $_;
+      } elsif( /^Summary: / ) {
+        $Summary = $_;
+      } elsif( /^Message-ID: / ) {
+        $Message_ID = $_;
+      }
+  
+    }
+  }
+}
+
+&readMessage;
+
+print STDERR "$Message_ID, $Subject from $From \n";
diff --git a/stump/bin/needAck b/stump/bin/needAck
new file mode 100755 (executable)
index 0000000..9b464d3
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+#
+
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+require "$MNG_ROOT/bin/robomod.pl";
+
+###################################################################### checkAck
+# checks if poster needs ack
+sub checkAck {
+
+  $needAck = ! &nameIsInList( $From, "noack.list" );
+
+}
+
+
+######################################################################
+# Getting data
+sub readMessage {
+
+  $IsBody = 0;
+  
+  while( <> ) {
+    $Body .= $_;
+
+    chop;
+  
+    if( /^$/ ) {
+      $IsBody = 1;
+    }
+  
+    if( !$IsBody ) {
+  
+      if( /^Newsgroups: / ) {
+        $Newsgroups = $_;
+        $Newsgroups =~ s/^Newsgroups: //i;
+      } elsif( /^Subject: / ) {
+        $Subject = $_;
+      } elsif( /^From: / ) {
+        $From = $_;
+      } elsif( /^Path: / ) {
+        $Path = $_;
+      } elsif( /^Keywords: / ) {
+        $Keywords = $_;
+      } elsif( /^Summary: / ) {
+        $Summary = $_;
+      } elsif( /^Message-ID: / ) {
+        $Message_ID = $_;
+      }
+  
+    }
+  }
+
+  close( TMPFILE );
+}
+
+######################################################################
+# read the thing
+&readMessage();
+
+######################################################################
+# process acks
+&checkAck;
+
+if( $needAck ) {
+  exit 0;
+} else {
+  exit 1;
+}
diff --git a/stump/bin/noAck b/stump/bin/noAck
new file mode 100755 (executable)
index 0000000..8fde0a5
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if [ "x$@" = "x" ]; then
+
+  cat >> $MNG_ROOT/data/noack.list
+
+else
+
+  echo $@ >> $MNG_ROOT/data/noack.list
+
+fi
diff --git a/stump/bin/preApprove b/stump/bin/preApprove
new file mode 100755 (executable)
index 0000000..bb0baa5
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if [ "x$@" = "x" ]; then
+
+  cat >> $MNG_ROOT/data/good.guys.list
+
+else
+
+  echo $@ >> $MNG_ROOT/data/good.guys.list
+
+fi
diff --git a/stump/bin/processApproved b/stump/bin/processApproved
new file mode 100755 (executable)
index 0000000..9cdf3c6
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+NEWSBIN=/var/lib/newsbin; export NEWSBIN
+shift
+DATE_STAMP="`date +%y%m%d%H%M%S`"
+
+TMPFILE=$TMP/pa.$DATE_STAMP.$$
+
+if [ $PGP = "none" ]; then
+  PMAPP_PROG=cat
+else
+  PMAPP_PROG="$PMAPP $NEWSGROUP"
+fi
+
+echo Action: processApproved 1>&2
+
+cat > $TMPFILE
+
+save() { 
+
+  ( 
+    cat $TMPFILE
+  ) | procmail -f- $MNG_ROOT/etc/procmail/save-approved
+}
+
+post() {
+  FAILED=$TMP/failed.$$
+  (
+    echo Path: "$PATH_SUFFIX"
+
+    cat $MNG_ROOT/etc/added-headers | grep ': ' 
+    # I do grep above because a lot of users inserts empty
+    # lines in the added headers.
+
+    echo Date: `date -R`
+    cat $TMPFILE                                       \
+      | formail -f -a "Newsgroups: $NEWSGROUP"         \
+           -I Path:                                    \
+            -I X-Moderate-For:                          \
+            -I Return-Path:                             \
+            -I X-Mailer:                                \
+           -I "Date:"                                  \
+            -I "X-400-Received:"                        \
+           -I Received: -I "From "                     \
+           -a "Approved: $PMUSER_APPROVAL"             \
+            -I Lines:                                   \
+            -I Cc:                                      \
+           -I To: -I Status:                           \
+           -I "X-Delivered-To:"                        \
+           -I "X-Envelope-To:"                         \
+           -I "X-Forwarding-To:"                       \
+           -I "X-Gradwell-Mailfilter:"                 \
+           -I "Delivered-To:"                          \
+           -I "Envelope-To:"                           \
+           -I "X-Priority:"                            \
+           -I "X-Priority:"                            \
+           -I "X-Priority:"                            \
+           -I "X-MSMail-Priority:"                     \
+           -I "X-MimeOLE:"                             \
+           -I "X-RBL-Warning:"                         \
+           -I "X-Scanner:"                             \
+           -I "X-Spam-Checker-Version:"                \
+           -I "X-Spam-Flag:"                           \
+           -I "X-Spam-Level:"                          \
+           -I "X-Spam-Report"                          \
+           -I "X-Spam-Score:"                          \
+           -I "X-Spam-Status:"                         \
+           -I "X-Scanned-By:"                          \
+           -I "X-Virus-Scanned:"                       \
+           -I "X-Virus-Status:"                        \
+           -I "X-Original-To:"                         \
+           -I "X-UID:"                                 \
+           -I "Delivered-To:"                          \
+           -I "DomainKey-Signature:"                   \
+           -I "Thread-Index:"                          \
+           -I "X-X-Sender:"                            \
+           -I "X-PMX-Version:"                         \
+
+    if [ -f $MNG_ROOT/etc/added-footer ] ; then
+      cat $MNG_ROOT/etc/added-footer
+    fi
+
+  )                                                     \
+    | $PMAPP_PROG                                      \
+    | tee $FAILED                                       \
+    | $RNEWS
+  if [ "$?" = "0" ] ; then
+    /bin/rm $FAILED
+  else
+    echo IHAVE failed. Look at $FAILED. 1>&2
+  fi
+}
+
+PMUSER="$PMUSER_APPROVAL"; export PMUSER
+ROBOMOD="$ROBOMOD_APPROVAL"; export ROBOMOD
+save
+post
+
+if needAck < $TMPFILE; then
+  modack.approved $TMPFILE
+fi
+
+rm $TMPFILE
diff --git a/stump/bin/processNoack.pl b/stump/bin/processNoack.pl
new file mode 100755 (executable)
index 0000000..38c29fe
--- /dev/null
@@ -0,0 +1,39 @@
+#
+# Processes the "No Ack" request
+#
+
+# get the directory where robomod is residing
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+# common library
+require "$MNG_ROOT/bin/robomod.pl";
+
+$NoAckFile = "$MNG_ROOT/data/noack.list";
+
+$Argv = join( ' ', @ARGV );
+
+while( <STDIN> ) {
+  $From = $_ if( /^From: / );
+
+  chop;
+  last if( /^$/ );
+}
+
+$From =~ s/^From: //;
+if( $From =~ m/([\w-\.]*)\@([\w-\.]+)/ ) {
+  $From = "$1\@$2";
+} else {
+  print STDERR "From line `$From' is incorrect\n";
+  exit 0;
+}
+
+if( !&nameIsInList( $From, "noack.list" ) ) { # need to preapprove
+  print STDERR "Adding $From to the noack list...\n";
+  open( NOACK, ">>$NoAckFile" );
+    print NOACK "$From\n";
+  close( NOACK );
+} else {
+  print STDERR "$From already is in noack list\n";
+}
+
+1;
diff --git a/stump/bin/processPreapproved b/stump/bin/processPreapproved
new file mode 100755 (executable)
index 0000000..38eb714
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+#
+# Preapproves the person and gets his/her message posted via processApproved
+#
+
+# get the directory where robomod is residing
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+# common library
+require "$MNG_ROOT/bin/robomod.pl";
+
+$GoodGuys = "$MNG_ROOT/data/good.guys.list";
+
+$Argv = join( ' ', @ARGV );
+
+
+open( PROCESS_APPROVED, "|processApproved $Argv" );
+
+while( <STDIN> ) {
+  $From = $_ if( /^From: / );
+
+  print PROCESS_APPROVED;
+
+  chop;
+  last if( /^$/ );
+}
+
+while( <STDIN> ) { # Body
+  print PROCESS_APPROVED;
+}
+
+close PROCESS_APPROVED;
+
+$From =~ s/^From: //g;
+if( $From =~ m/([\w-\.]*)\@([\w-\.]+)/ ) {
+  $From = "$1\@$2";
+} else {
+  print STDERR "From line `$From' is incorrect\n";
+  exit 0;
+}
+
+if( !&nameIsInList( $From, "good.guys.list" ) ) { # need to preapprove
+  &logAction( "Action: processPreapproved $From\n" );
+  open( GOOD_GUYS, ">>$GoodGuys" );
+    print GOOD_GUYS "$From\n";
+  close( GOOD_GUYS );
+} else {
+  print STDERR "$From already preapproved\n";
+}
diff --git a/stump/bin/processRejected b/stump/bin/processRejected
new file mode 100755 (executable)
index 0000000..212a8f7
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# This script takes a raw article that is already rejected, creates a 
+# reply message, signs it with PGP and sends back to the author.
+#
+
+# echo $0 invoked with arguments $@ 1>&2
+
+MESSAGE=$TMP/rejected.$$
+
+shift; REASON="$1"; export REASON; shift 
+#EXPLANATION="$@"; export EXPLANATION
+
+echo Action: processRejected, reason=$REASON 1>&2
+
+cat  > $MESSAGE
+
+#save() { 
+#  procmail -p -f- $MNG_ROOT/etc/procmail/save-rejected < $MESSAGE
+#}
+
+reply() {
+
+  if [ "x$REASON" = xdiscard ]; then return; fi
+  (
+    cat $MESSAGE | formail -rt -I "Reply-To: $BOARD"   \
+                              -I "Errors-To: $MUNGED_ADDRESS"   \
+        -I "X-Webstump-Event: reject $REASON" \
+       -I 'Bcc: webstump+urcm-internal-log+mailout' \
+       -I 'Bcc: webstump+urcm-internal-reject-copy'
+    (
+      echo "$EXPLANATION"
+      echo
+
+      if [ "x$REASON" != "xcustom" ] ; then
+       cat $MNG_ROOT/etc/messages/$REASON
+      fi
+
+      echo ""
+      echo ============================================ Full text of your message follows
+      sed 's/^/> /; s/webstump+[-+/0-9a-z]*@chiark/webstump+?@chiark/' \
+         < $MESSAGE
+    ) | gpg --clearsign --textmode --armor --batch --user "$PMUSER_APPROVAL" \
+         --passphrase "$PMPASSWORD" 2>/dev/null \
+  ) | sendmail -oi -t -f $MUNGED_ADDRESS
+}
+
+update_rejection_count() {
+  # don't count forgeries and signature mismatches against the victim
+  # also don't count thread rejections or duplicates
+  if [ "x$REASON" != "xforgery" -a "x$REASON" != "xsignature" -a "x$REASON" != "xthread" -a "x$REASON" != "xduplicate" -a "x$REASON" != "xcrosspost" -a "x$REASON" != "xempty" ]; then
+    cat $MESSAGE | updateActionCount.pl -r
+  fi
+}
+
+#save
+reply $1
+#update_rejection_count
+rm $MESSAGE
+
+#    ) | stump-pgp -staf -z "$PMPASSWORD"      \
+#            -u "$ROBOMOD_APPROVAL" +clearsig=on  2>/dev/null  \
diff --git a/stump/bin/report.sh b/stump/bin/report.sh
new file mode 100755 (executable)
index 0000000..13138f1
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+# posts a nice report
+#
+# $Id: report.sh,v 1.2 2007/05/03 23:47:49 rram Exp $
+# Modified to work with GPG
+
+TODAY="`date`"
+DATE6="`date +%y%m%d`"
+
+LOGFILE="$HOME/Mail/from"
+LOGFILE_ARCHIVED="$MNG_ROOT/archive/old/from.$DATE6"
+
+Report() {
+  echo Subject: $NEWSGROUP report for $TODAY
+  echo Newsgroups: $NEWSGROUP
+  echo To: $SUBMIT
+  echo From: $ADMIN
+  echo Reply-To: $ADMIN
+  echo Organization: CrYpToRoBoMoDeRaToR CaBaL
+  echo ""
+
+(
+  echo Subject: $NEWSGROUP report for $TODAY
+  echo Newsgroups: $NEWSGROUP
+  echo Date: $TODAY
+  echo ""
+
+cat << _EOB_
+This is an automated report about activity of our newsgroup
+$NEWSGROUP. It covers period between the 
+previous report and the current one, ending 
+on $TODAY.
+
+Note that we do not report the number of articles cancelled
+after they got approved, because the cancellations are done
+manually. Typically messages get cancelled by requests of
+posters themselves.
+
+Lastly, the statistics below are skewed towards higher numbers because
+there are always some test messages from moderators themselves who
+approve and reject them to make sure that our robomoderator functions
+properly.
+
+_EOB_
+
+  stump-report.pl $LOGFILE
+) | stump-pgp --clearsign --textmode --armor --batch --user $PMUSER_APPROVAL --passphrase "$PMPASSWORD" 2>/dev/null
+}
+
+Report | sendmail -t
+
+mv $LOGFILE $LOGFILE_ARCHIVED
+gzip -9 $LOGFILE_ARCHIVED &
diff --git a/stump/bin/robomod.pl b/stump/bin/robomod.pl
new file mode 100755 (executable)
index 0000000..3534c5a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+#
+# Collection of common functions
+#
+
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+###################################################################### checkAck
+# checks if poster needs ack
+sub nameIsInList {
+  local( $listName ) = pop( @_ );
+  local( $address ) = pop( @_ );
+
+  local( $item );
+
+  $Result = 0;
+
+  open( LIST, "$MNG_ROOT/data/$listName" );
+
+  while( $item = <LIST> ) {
+
+    chop $item;
+
+    next if $item =~ /^ *$/;
+
+    if( eval { $address =~ /$item/i; } || "\L$address" eq "\L$item" ) {
+      $Result = $item;
+    }
+  }
+
+  close( LIST );
+
+  return $Result;
+}
+
+sub logAction {
+  my $msg = pop( @_ );
+
+  print STDERR $msg . "\n";
+}
+
+
+######################################################################
+# Setting variables
+
+if( defined( $ENV{'STUMP_PARANOID_PGP'} ) ) {
+  $paranoid_pgp = $ENV{'STUMP_PARANOID_PGP'}  eq "YES";
+} else {
+   $paranoid_pgp = 0;
+}
+
+1;
diff --git a/stump/bin/send_pgp_key b/stump/bin/send_pgp_key
new file mode 100755 (executable)
index 0000000..4104f1b
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+(
+  formail -r -I "Subject: $NEWSGROUP Approval PGP Key" \
+         -I "Reply-To: devnull@algebra.com"            \
+         -I "Errors-To: devnull@algebra.com"
+
+  cat << _EOB_
+Hello,
+
+Thank you for requesting PGP Public Key used to sign submissions to
+soc.culture.russian.moderated newsgroup. Please use PGP Moose scripts
+to verify integrity of robomod's signatures on your news articles and
+report any articles that fail verification to scrm-admin@algebra.com.
+
+Thanks,
+
+       - Your Friendly $NEWSGROUP Robomoderator.
+
+_EOB_
+  cat $MNG_ROOT/approval.key.txt
+) | /usr/sbin/sendmail -t
diff --git a/stump/bin/stump-pgp b/stump/bin/stump-pgp
new file mode 100755 (executable)
index 0000000..05425d3
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# $Id: stump-pgp,v 1.2 2007/05/03 23:47:55 rram Exp $
+# Modified to work with GPG
+
+if [ "$PGP" = "none" ] ; then
+  cat
+else 
+  if [ "x$PGP" = "x" ] ; then
+    if which gpg ; then
+      gpg $@
+    else
+      cat $@
+      echo used cat because gpg was not found 1>&2
+    fi
+  else
+    if [ -x $PGP ] ; then
+      $PGP $@
+    else
+      echo Please define variable PGP in your admin/etc/modenv file 1>&2
+      gpg $@
+    fi
+  fi
+fi
diff --git a/stump/bin/stump-report.pl b/stump/bin/stump-report.pl
new file mode 100755 (executable)
index 0000000..253065d
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+#
+# This script processes the logfile given as it first command argument, 
+# and prints a nice report about approved, rejected, etc postings, to
+# stdout.
+#
+# is called from report.sh
+#
+
+# get the directory where robomod is residing
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+# common library
+require "$MNG_ROOT/bin/robomod.pl";
+
+$logFile = $ARGV[0]   || die "A log file name must be specified";
+open( LOG, $logFile ) || die "Can't open logfile $logFile for reading";
+
+$approvedCount = 0;
+$autoCount     = 0;
+$rejectedCount = 0;
+$preApprovedCount = 0;
+
+while( <LOG> ) {
+  $approvedCount++    if( /processApproved/i );
+  $autoCount++        if( /PREAPPROVED/ );
+  $rejectedCount++    if( /processRejected/i );
+#  $approvedCount++    if( /processPreapproved/i );
+  $preApprovedCount++ if( /processPreapproved/i );
+}
+
+print "
+
+Approved:       $approvedCount         messages (of them, $autoCount automatically)
+Rejected:       $rejectedCount         messages
+Preapproved:    $preApprovedCount      new posters
+";
diff --git a/stump/bin/stump.pl b/stump/bin/stump.pl
new file mode 100755 (executable)
index 0000000..3dfa8bc
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+$MNG_ROOT=$ENV{'MNG_ROOT'} 
+       || die "Newsgroup Root Directory (\$MNG_ROOT) Not Defined!";
+
+
+sub start {
+
+  my $robomod_pl = "$MNG_ROOT/bin/robomod.pl";
+  require "$robomod_pl" if( -f $robomod_pl && -r $robomod_pl );
+
+  my $script = shift @ARGV
+       || die "Syntax: $0 script-name [parameters]\n";
+  my $script_file = "$MNG_ROOT/bin/$script";
+  if( -f $script_file ) {
+    require $script_file;
+  } else {
+    die "ERROR: $script_file not found and could not be executed.";
+  }
+}
+
+start;
diff --git a/stump/bin/submission.pl b/stump/bin/submission.pl
new file mode 100755 (executable)
index 0000000..f978a1f
--- /dev/null
@@ -0,0 +1,465 @@
+#
+# this script accepts submissions that come to the robomoderator by
+# email. It make s a decision whether the submission deserves
+# rejection, automatic approval, or is suspicious and should be
+# forwarded to human moderators for review.
+#
+# A decision is essentially a choice of the program that will be
+# fed with preprocessed article.
+#
+# Also note that this script fixes common problems and mistakes in
+# newsreaders, newsservers, and users. Even though we have no
+# obligation to fix these problems, people get really disappointed
+# if we outright reject their bogus messages, because these
+# people often have no control over how the posts get delivered to us.
+#
+# This script supports notion of blacklisting and list of
+# preapproved persons. As the names imply, we reject all submissions
+# from blacklisted posters and automatically approve all messages submitted
+# by preapproved posters (provided that their posts meet other criteria
+# imposed by the robomoderator.
+#
+# For an automatic rejection, it gives a main "reason" for rejection
+#
+#Currently supported list of reasons: 
+#
+#      - crosspost
+#      - abuse
+#      - harassing
+#      - offtopic
+
+# get the directory where robomod is residing
+$MNG_ROOT = $ENV{'MNG_ROOT'} || die "Root dir for moderation not specified";
+
+# common library
+require "$MNG_ROOT/bin/robomod.pl";
+
+# max allowed number of newsgroups in crossposts
+# change it if you want, but 5 is really good.
+$maxNewsgroups = $ENV{'MAX_CROSSPOSTS'} || 5; 
+
+# should we ALWAYS require preapproved posters to sign their submissions
+# with PGP? Turn it on in the `etc/modenv' if you suffer from numerous
+# forgeries in the names of preapproved posters.
+$PGPCheckPreapproved = $ENV{ "WHITELIST_MUST_SIGN" } eq "YES";
+
+# So, what newsgroup I am moderating?
+$Newsgroup = $ENV{'NEWSGROUP'};
+
+# as the name implies. ATTENTION: $TMP must be mode 700!!!
+$TmpFile = "$ENV{'TMP'}/submission.$$";
+
+# how do we treat suspicious articles?
+$Command_Suspicious = "formail -a \"Newsgroups: $Newsgroup\" " .
+                      "| stump.pl suspicious.pl";
+
+# approved
+$Command_Approve = "processApproved robomod";
+# rejected
+$Command_Reject  = "processRejected robomod";
+
+# location of blacklist
+$badGuys = "bad.guys.list";
+
+# location of preapproved list
+$goodGuys = "good.guys.list";
+
+# words that trigger robomod to mark messages suspicious, even 
+# when the message comes from a preapproved person.
+$badWords = "bad.words.list";
+
+# list of people who want all their submissions to be signed
+$PGPMustList = "pgp.must.list";
+
+# set PMUSER and ROBOMOD to Internal. Will be used by `suspicious' script.
+$ENV{'PMUSER'} = $ENV{'PMUSER_INTERNAL'};
+$ENV{'ROBOMOD'} = $ENV{'ROBOMOD_INTERNAL'};
+
+
+######################################################################
+# Filter rules
+# checks if all is OK with newsgroups.
+# what's not OK: 
+#   1. Megacrossposts
+#   2. Crossposts to other moderated groups
+#   3. Control messages (currently)
+#
+sub checkNewsgroups {
+
+  # We have not implemented Control: yet...
+  if( $Control ) {
+print STDERR "CONTROL message - rejected\n";
+    return "$Command_Reject crosspost You posted a Control message which " .
+           "is not allowed.";
+  }
+
+  if( $#newsgroups >= $maxNewsgroups ) {
+print STDERR "Too many newsgroups\n";
+    return "$Command_Reject crosspost Too many newsgroups, " .
+           "$maxNewsgroups is maximum.";
+  }
+
+  local( $good ) = 0;
+
+  for( $i = 0; $i <= $#newsgroups; $i++ ) {
+
+    if( $newsgroups[$i] eq $Newsgroup ) {
+      $good = 1;
+      next;
+    }
+
+    if( $NewsgroupsDB{$newsgroups[$i]} eq 'm' && 
+        $newsgroups[$i] ne $Newsgroup) {
+print STDERR "posting to ANOTHER moderated newsgroups\n";
+      return "$Command_Reject crosspost You crossposted to another " .
+             "moderated newsgroup.";
+    }
+
+  }
+
+  if( !$good ) { # Some fool forgot to list the moderated newsgroup
+                 # in the Newsgroups
+    $Newsgroups .= ",$Newsgroup";
+    if( $#newsgroups + 1 >= $maxNewsgroups ) {
+print STDERR "Too many newsgroups\n";
+      return "$Command_Reject crosspost Too many newsgroups, FIVE is maximum.";
+    }
+  }
+
+  return 0;
+}
+
+###################################################################### checkAck
+# checks if poster needs acknowledgment of receipt
+#
+sub checkAck {
+  if( &nameIsInList( $From, "noack.list" ) ) {
+    $needAck = "no";
+  } else {
+    $needAck = "yes";
+  }
+}
+
+################################################################### checkPGP
+# checks PGP sig IF REQUIRED
+#
+# we can reject a post if
+#
+#   1. A post must be signed accordinng to rules OR
+#   2. A post is signed but verification fails.
+#
+# Note that we set From: to the user ID in the PGP signature
+# if a signature is present. It allows for identification of trolls
+# and for preventing subtle forgeries.
+#
+sub checkPGP {
+
+  local( $FromSig ) = `verifySignature < $TmpFile`; chop( $FromSig );
+  local( $good ) = $? == 0;
+
+print STDERR "FromSig = $FromSig, good = $good\n" if $FromSig;
+
+  if( !$good ) {
+    return "$Command_Reject signature Your PGP signature does NOT match, or is not in our keyring";
+  }
+
+  if( &nameIsInList( $From, $PGPMustList ) ||
+      ($PGPCheckPreapproved && &nameIsInList($From, $goodGuys) ) ) {
+    if( $FromSig eq "" ) {
+      return "$Command_Reject signature You are REQUIRED to sign your posts.";
+    } 
+  }
+
+  if( $FromSig ) {
+    $X_Origin = $From;
+    $From = "From: $FromSig";
+    $ReplyTo = $From;
+  }
+
+  # else nothing to do
+  return 0;
+}
+
+################################################################ checkCharter
+# checks charter calling conforms_charter
+#
+sub checkCharter {
+  open( VERIFY, "|conforms_charter" );
+  print VERIFY $Body;
+  close( VERIFY );
+
+  return $? == 0;
+}
+
+################################################################### Filter
+# contains all filtering rules. calls subroutines above.
+sub Filter {
+
+
+  local( $response );
+
+  @newsgroups = split( /,/, $Newsgroups );
+
+  return "Command_Reject charter We do not allow any control and " .
+         "cancel messages. contact newsgroup administrator" 
+    if( $Control );
+
+  if( $response = &checkNewsgroups() ) {
+      return $response;
+  }
+
+  if( $paranoid_pgp ) {
+    if( $response = &checkPGP() ) {
+        return $response;
+    }
+  }
+
+  if( &nameIsInList( $From, $badGuys ) ) {
+    return "$Command_Reject abuse";
+  }
+
+  # note that if even a preapproved person uses "BAD words" (that is
+  # words from a special list), his/her message will be marked
+  # "suspicious" and will be forwarded to a humen mod for review.
+  # As an example of a bad word may be "MAKE MONEY FAST - IT REALLY WORKS!!!"
+  #
+  if( $badWord = &nameIsInList( $Body, $badWords ) ) {
+print STDERR "BAD WORD $badWord FOUND!!!\n";
+    return $Command_Suspicious; # messages from approved guys MAY be 
+                         # suspicious if they write about
+                         # homosexual forgers
+  }
+
+  # checking for charter-specific restrictions
+  if( !&checkCharter || ($Encoding =~ "base64") ) {
+    return "$Command_Reject charter you sent a " .
+           "binary encoded file which is not allowed.";
+  }
+
+  # Checking preapproved list
+  if( &nameIsInList( $From, $goodGuys ) ) {
+  local( $from ) = $From; $from =~ s/^From: //i;
+print STDERR "$from is a PREAPPROVED person\n";
+    return $Command_Approve;
+  }
+
+  # Here I may put some more rules...
+
+  return $Command_Suspicious;
+}
+
+######################################################################
+# set defaults
+sub setDefaults {
+  if( !$Newsgroups ) {
+    $Newsgroups = $ENV{ "NEWSGROUP" } || die "No default newsgroup";
+  }
+}
+
+################################################################# ignoreHeader
+# some of the header fields present in emails must be ignored.
+#
+sub ignoreHeader {
+  local( $header ) = pop( @_ );
+
+#  return 1 if( $header =~ /^Control:/i );
+  return 1 if( $header =~ /^Expires:/i );
+  return 1 if( $header =~ /^Supersedes:/i );
+  return 1 if( $header =~ /^Precedence:/i );
+  return 1 if( $header =~ /^Apparently-To:/i );
+  return 1 if( $header =~ /^Date:/i );
+  return 1 if( $header =~ /^Expires:/i );
+  return 1 if( $header =~ /^Distribution:/i );
+  return 1 if( $header =~ /^Path:/i );
+  return 1 if( $header =~ /^NNTP-Posting-Host:/i );
+  return 1 if( $header =~ /^Xref:/i );
+  return 1 if( $header =~ /^Status:/i );
+  return 1 if( $header =~ /^Lines:/i );
+  return 1 if( $header =~ /^Apparently-To:/i );
+  return 1 if( $header =~ /^Cc:/i );
+  return 1 if( $header =~ /^Sender:/i );
+  return 1 if( $header =~ /^In-Reply-To:/i );
+  return 1 if( $header =~ /^Originator:/i );
+
+  return 0;
+}
+
+
+######################################################################
+# Getting data
+# 
+# reads message, sets variables describing header fields
+#
+# it also tries to "fix" the problem with old newsservers (B-News I think)
+# when they try to "wrap" a submission in one more layer of meaningless
+# headers. It is recognized by STUPID presense of TWO identical To: 
+# fields.
+#
+
+sub readMessage {
+
+open IWJL, ">>/home/webstump/t.log";
+print IWJL "=========== SUBMISSION READMESSAGE\n";
+
+  open( TMPFILE, "> $TmpFile" );
+
+  $IsBody = 0;
+  
+  while( <> ) {
+print IWJL "SbRm $_\n";
+    $Body .= $_;
+
+    if( !$IsBody && &ignoreHeader( $_ ) ) {
+      next;
+    }
+
+    print TMPFILE;
+  
+    chop;
+  
+    if( /^$/ ) {
+      if( !$Subject && $From =~ /news\@/) {
+        $BadNewsserver = 1;
+      }
+
+      if( $BadNewsserver ) { # just ignore the outer layer of headers
+        $To = 0;
+      } else {
+        $IsBody = 1;
+      }
+    }
+  
+    if( !$IsBody ) {
+  
+      if( /^Newsgroups: / ) { # set Newsgroups, remove spaces
+        $Newsgroups = $_;
+        $Newsgroups =~ s/^Newsgroups: //i;
+        $Newsgroups =~ s/ //g; # some fools put spaces in list of newsgroups
+      } elsif( /^Subject: / ) {
+        $Subject = $_;
+      } elsif( /^From: / ) {
+        $From = $_;
+      } elsif( /^To: / ) {
+        if( $To && ($To eq $_)) { 
+          # Old & crappy news servers that wrap submissions with one more
+          # layer of headers. For them, I simply ignore the outer
+          # headers. These (at least I think) submissions may be
+          # recognized by TWO idiotic To: header fields.
+print STDERR "BAD NEWSSERVER\n";
+          $BadNewsserver = 1;
+        }
+        $To = $_;
+      } elsif( /^Path: / ) {
+        $Path = $_;
+      } elsif( /^Keywords: / ) {
+        $Keywords = $_;
+      } elsif( /^Summary: / ) {
+        $Summary = $_;
+      } elsif( /^Control: / ) {
+        $Control = $_;
+      } elsif( /^Message-ID: / ) {
+        $Message_ID = $_;
+      } elsif ( /^Content-Transfer-Encoding: / ) {
+        $Encoding = $_;
+        $Encoding =~ s/^Content-Transfer-Encoding: //;
+      }
+  
+    }
+  }
+use IO::Handle;
+  print IWJL "SbRmE $!\n";
+  die "read message $! !" if STDIN->error;
+
+  close( TMPFILE );
+}
+
+###################################################################### work
+# all main work is done here
+
+######################################################################
+# read the thing
+&readMessage();
+
+if( !$Newsgroups ) {
+  $Newsgroups = $Newsgroup;
+}
+
+######################################################################
+# process acks
+&checkAck;
+$Command_Suspicious .= " $needAck";
+
+######################################################################
+# set defaults
+&setDefaults();
+
+######################################################################
+# Check
+
+$command = &Filter;
+
+######################################################################
+# process
+print STDERR "command = $command\n";
+
+open IWJL, ">>/home/webstump/t.log";
+print IWJL "=========== SUBMISSION MAIN\n";
+
+open( COMMAND, "| $command" );
+open( TMPFILE, "$TmpFile" ) || die "cant open tmpfile";
+
+  $IsBody = 0;
+
+  while( <TMPFILE> ) {
+print IWJL "SbRt $_\n";
+
+    if( $BadNewsserver && !(/^$/) ) {
+      next;
+    }
+
+    if( $BadNewsserver && /^$/ ) {
+      $BadNewsserver = 0;
+      next;
+    }
+
+    if( /^$/ ) {
+      $IsBody = 1;
+    }
+
+    if( /^From / ) {
+      print COMMAND;
+      print COMMAND "X-Origin: $X_Origin, $_" if $X_Origin;
+      print STDERR "Subject =`$Subject'\n";
+      print COMMAND "Subject: No subject given\n" if !$Subject;
+      # nothing
+    } elsif( /^From: / && !$IsBody) {
+      next if $FromWasUsed;
+
+      $FromWasUsed = 1; # note that some crappy remailers have several
+                        # "From: " fields. We really do NOT want two
+                        # "From: " to go to headers!
+
+      if( $From ) {
+        print COMMAND "$From\n";
+        $From = "";
+      } else {
+        print COMMAND;
+      }
+    } elsif( /^Newsgroups: / && !$IsBody ) {
+      print COMMAND "Newsgroups: $Newsgroups\n";
+    } else {
+      print COMMAND;
+    }
+  }
+
+close( TMPFILE );
+close( COMMAND );
+
+################################################################## Archiving
+# archive
+
+#open( COMMAND, "| procmail -f- $MNG_ROOT/etc/procmail/save-incoming" );
+#print COMMAND $Body;
+#close( COMMAND );
+
+unlink( $TmpFile );
diff --git a/stump/bin/submitFailed b/stump/bin/submitFailed
new file mode 100755 (executable)
index 0000000..938d341
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+cd $MNG_ROOT/tmp
+
+for i in failed*; do 
+  if $RNEWS < $i; then
+    /bin/rm $i
+  fi
+done
diff --git a/stump/bin/suspicious.pl b/stump/bin/suspicious.pl
new file mode 100755 (executable)
index 0000000..00654b3
--- /dev/null
@@ -0,0 +1,179 @@
+#
+# this script processes "suspicious" messages. It does basically two things:
+#
+# 1. Sends an acknowledgment to the author, if necessary (ie if the author did
+#    not turn ack mode off), and
+# 2. Prepares, signs, and sends the message to a randomly chosen human 
+#    moderator for review.
+#
+
+$MNG_ROOT = $ENV{'MNG_ROOT'};
+$Prefix   = $ENV{'BOT_SUBJECT_PREFIX'};
+$tempFile = "$ENV{'TMP'}/signed.$$";
+
+# name of moderator to test
+$TestModerator = "eugenez\@mit.edu";
+
+$isTesting = 0; # No testing by default
+$TestFlag="test-test";
+
+$needAck = $ARGV[0]; shift @ARGV;
+
+$message = join( " ", @ARGV );
+
+print STDERR "Needack = $needAck\n";
+
+
+######################################################################
+# Getting data
+sub readMessage {
+  $IsBody = 0;
+  
+open IWJL, ">>/home/webstump/t.log";
+print IWJL "=========== SUSPICIOUS READMESSAGE\n";
+
+  while( <STDIN> ) {
+print IWJL "SsRm $_\n";
+
+    $Body .= $_;
+
+    $isKOI8 = $isKOI8 || $_ =~ /[\x80-\xFF]/;
+    chop;
+  
+    if( /^$/ ) {
+      $IsBody = 1;
+    }
+  
+    if( !$IsBody ) {
+  
+      if( /^Newsgroups: / ) {
+        $Newsgroups = $_;
+      } elsif( /^Subject: / ) {
+        $Subject = substr( $_, 0, 50 );
+
+        if( $Subject =~ /$TestFlag/i ) { # special subject
+          $isTesting = 'y';
+        }
+
+      } elsif( /^From: / ) {
+        $From = $_;
+       if ($From =~ m/([\w-\.]+)@([\w-\.]*)[^\w-\.]/){
+           $From = "From: $1\@$2";}
+      } elsif( /^Path: / ) {
+        $Path = $_;
+      } elsif( /^Keywords: / ) {
+        $Keywords = $_;
+      } elsif( /^Summary: / ) {
+        $Summary = $_;
+      } elsif( /^Message-ID: / ) {
+        $Message_ID = $_;
+      }
+  
+    }
+  }
+}
+
+$MNG_ROOT = "$ENV{'MNG_ROOT'}" || die "Root dir for moderation not specified";
+
+&readMessage;
+
+#$Body =~ s/From /_From /g;
+
+
+if( $isTesting eq 'y' ) {
+  $moderator = $From; # $TestModerator;
+  $moderator =~ s/^From: //i;
+
+  } else { # get random moderator
+  open( MODERATORS, "$MNG_ROOT/etc/moderators" ) 
+    || die "can't open moderators file";
+
+  while( <MODERATORS> ) {
+    next if( /^#/ );
+    next if( /^\s*$/ );
+    ($name, $priority, $flags) = split;
+  
+    if( $priority != 0 && ! ($isKOI8 && $flags =~ /nokoi/i)) { 
+    # priority 0 means "on vacation"
+      push( @moderators, $name );
+    }
+  }
+
+  close( MODERATORS );
+  srand;
+
+  $randNum = rand 100 + time;
+
+  $moderator = $moderators[ $randNum % ($#moderators + 1) ];
+}
+
+
+$modNick = $moderator;
+
+$modNick =~ s/@.*//;
+$modNick =~ s/-.*//;
+
+$date = `date`; chop $date;
+
+print STDERR "Activity: $date Forwarding $Message_ID, $Subject from $From ".
+             "to $moderator for approval\n";
+
+$MessageNumber = time . $$;
+
+print STDERR "Opening $MNG_ROOT/tmp/messages/$MessageNumber\n";
+
+open( MESSAGE, "> $MNG_ROOT/tmp/messages/$MessageNumber" );
+print MESSAGE $Body;
+close( MESSAGE );
+
+$Subject = "Subject: try again" if( !$Subject );
+
+open( COMMAND, "| sendmail $moderator > /dev/null"  );
+
+print COMMAND "From: $ENV{'DECISION_ADDRESS'}
+$Subject ::$Prefix/$MessageNumber
+To: $moderator
+X-Moderate-For: $ENV{'NEWSGROUP'}
+Organization: Robots Are Us
+
+$message
+
+This is an automated message sent to you as the moderator of
+$ENV{'NEWSGROUP'}. Simply reply to this message,
+DO NOT quote the message body in your reply, and state your decision ON
+THE FIRST LINE, choosing LITERALLY from ONE of the following options
+(you can even cut and paste):
+
+approve
+preapprove
+";
+
+open( REASONS, "$MNG_ROOT/etc/rejection-reasons.lst" );
+while( <REASONS> ) {
+  ($reason, $explanation) = split( /::/, $_ );
+  print COMMAND "reject $reason\n";
+}
+close( REASONS );
+
+print COMMAND "
+
+You can also specify a comment, by typing word comment at the
+beginning of a line _after_ approve, preapprove, or reject, and
+then your comment on one or several lines. Do NOT put your comment
+before approve, preapprove, or reject.
+
+Message follows:
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+";
+
+print COMMAND $Body;
+close( COMMAND );
+
+if( $needAck eq "yes" ) {
+  open( ACK, "| modack.received" );
+  print ACK $Body;
+  close( ACK );
+}
+
+1;
diff --git a/stump/bin/verifySignature b/stump/bin/verifySignature
new file mode 100755 (executable)
index 0000000..45571a3
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# $Id: verifySignature,v 1.2 2007/05/03 23:50:21 rram Exp $
+# Modified to work with GPG
+
+NAME="$1"
+
+TMPFILE=$TMP/vrfy.$$
+
+cat > $TMPFILE
+
+if grep -e "^$BEGIN_PGP_SIGNED_MESSAGE" < $TMPFILE >/dev/null; then
+  SIGNED="YES"
+else
+  SIGNED="NO"
+fi
+
+if [ $SIGNED = "NO" ] ; then 
+  if [ "x$1" != "x" ] ; then
+    echo "No valid sig!" 1>&2
+    rm $TMPFILE
+    exit 1
+  else 
+    rm $TMPFILE
+    exit 0
+  fi
+fi
+
+#
+# this we do under assumption that message IS signed
+#
+
+GOODSIG="gpg: Good signature from \""
+
+DoPGP() {
+#  stump-pgp -f +batchmode=on +pubring=$MNG_ROOT/data/pubring.pgp < $TMPFILE 2>&1 > /dev/null
+   stump-pgp --no-default-keyring --keyring $MNG_ROOT/data/pubring.gpg < $TMPFILE 2>&1 > /dev/null
+}
+
+USERID="`DoPGP | grep -e "^$GOODSIG" | sed \"s/^$GOODSIG//\" | sed 's/"\.$//'`"
+
+
+rm $TMPFILE
+
+echo USERID = "$USERID" 1>&2
+
+if [ "x$1" != "x" ]; then
+  if [ "$USERID" = "$1" ] ; then
+    exit 0
+  else
+    exit 1
+  fi
+else
+  if [ "x$USERID" = "x" ]; then # bad sig!
+    exit 1;
+  fi
+
+  echo "$USERID"
+  exit 0
+fi
diff --git a/stump/c/antivirus.c b/stump/c/antivirus.c
new file mode 100644 (file)
index 0000000..a676c94
--- /dev/null
@@ -0,0 +1,101 @@
+/* antivirus.c - 
+
+   This program replaces all "dangerous" characters in the incoming file
+   to '_' character. Dangerous characters are all characters less than 32 
+   (space) and not equal to \n, \r, \t, \f and ^H.
+
+   It also notices lines > 1024 characters and splits them, adding space
+   character in front of the split line.
+
+   In ``fascist'' mode (when called with argument -fascist), it is much
+   more restrictive: it permits only newlines, space, TAB, lowercase and
+   uppercase letters, and digits. Everything else is replaced by '_'
+   character.  Fascist mode should be used to filter user input that is
+   to be used in shell scripts. It may prevent users from being able to
+   fool poorly written shell scripts that accept user commands into
+   executing arbitrary programs. Since, unfortunately, it is likely that
+   some of the scripts would be prone to such attacks, using this
+   program is highly recommended BEFORE doing anything with user input.
+
+   This program should be used to preprocess all incoming mail before 
+   feeding to mail processing scripts. It in fact may prove useful 
+   against viruses exploiting weaknesses of C programs that overflow
+   buffers, etc.
+
+   Copyright 1996, Igor Chudov. GNU Public license applies, and I am
+   not responsible for any damage arising from use of this program.
+
+*/
+
+#include <stdio.h>
+
+#define MAX_CHAR 256 /* max unsigned char */
+#define MAX_LEN 1024 /* max allowed line size */
+#define NEWLINE "\n" /* newline for Unix, for DOS I think "\r\n" */
+
+unsigned char charTable[ MAX_CHAR ];
+
+#define SET_GOOD_INTERVAL( l, u ) \
+  for( i = l; i <= u; ) charTable[i++] = 1;
+
+void initCharTable( int fascist )
+{
+  int i;
+
+  /* bad characters can only be used for viruses */
+  for( i=0; i < MAX_CHAR; i++ ) charTable[i] = 0;
+
+  charTable['\n'] = 1;
+  charTable[' ']  = 1;
+  charTable['\r'] = 1;
+  charTable['\t'] = 1;
+  charTable['\f'] = 1;
+
+  if( fascist ) { /* fascist mode - used for shells */
+    SET_GOOD_INTERVAL( 'a', 'z' );
+    SET_GOOD_INTERVAL( 'A', 'Z' );
+    SET_GOOD_INTERVAL( '0', '9' );
+    charTable['-'] = 1;
+    charTable['+'] = 1;
+    charTable['.'] = 1;
+    charTable[','] = 1;
+  } else { /* normal mode - used to filter users' mail. */
+
+    /* Good characters, incl Cyrillic */
+    SET_GOOD_INTERVAL( ' ', MAX_CHAR-1 );
+  
+    charTable[8] = 1;        /* 8 is Ctrl-H, BackSpace */
+  }
+}
+
+int main( int argc, char **argv )
+{
+  int ch, len = 0;
+  int fascist = 0;
+
+  if( argc == 2 ) {
+    if( strcmp( argv[1], "-fascist" ) ) {
+      fprintf( stderr, "Usage: %s [-fascist]\n", argv[0] );
+      return( 1 );
+    }
+    fascist = 1;
+  } else if( argc != 1 ) {
+    fprintf( stderr, "Usage: %s [-fascist]\n", argv[0] );
+    return( 1 );
+  }
+
+  initCharTable( fascist );
+
+  while( (ch = getchar()) != EOF )
+    {
+      if( !charTable[ch] ) ch = '_';
+      if( ch == '\n' ) len = 0; else len++;
+      if( len > MAX_LEN ) {
+        printf( NEWLINE " " );
+        len = 1;                 /* because I put " " */
+      }
+      putchar( ch );
+    }
+
+  return 0;
+}
diff --git a/stump/c/checkquot.c b/stump/c/checkquot.c
new file mode 100644 (file)
index 0000000..268a208
--- /dev/null
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+
+main(int argc, char *argv[])
+{
+  double Qratio;
+  int MaxLines;
+
+  FILE *fp;
+  char buf[100];
+  int i, lines=0, prefixes[256];
+  
+  /* initialize array of # of prefixes (for each ascii char) */
+  for(i=0; i<256; prefixes[i]=0, i++);
+
+  /* set values for quote ratio and max #lines */
+  Qratio   = argc>1 ? atof(argv[1]) : .75 ;
+  MaxLines = argc>2 ? atoi(argv[2]) : 20 ;
+      
+  /* open input file or stdin */
+  if(argc<=3 || ! ( fp = fopen(argv[3], "r") ) )
+    fp = stdin;
+  
+  /* read input  */
+  while( fgets(buf, 99, fp) )
+    {
+      int i;
+
+      /* get to the first non-white char in line */
+      for(i=0; buf[i] < 33 && buf[i] > 0; i++);
+
+      /* string is non-empty */
+      if( buf[i] != '\0' )
+       {
+         /* increment prefix counter for this prefix */
+         prefixes[ (int) buf[i] ]++;
+      
+         /* increment line counter */
+         lines++;
+       }
+    }
+  
+    if( lines > MaxLines )
+      for( i=0; i<256; i++ )
+
+       /* overquote with char i */
+       if( prefixes[i] > Qratio*lines )
+           exit(1);
+
+  /* everytjing seems ok */
+  exit(0);
+}
+
diff --git a/stump/c/compile b/stump/c/compile
new file mode 100755 (executable)
index 0000000..dfa0e7b
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# define your compiler
+CC=cc
+
+for i in checkquot antivirus cuthead isbinary; do
+  echo -n Compiling $i.c with $CC...
+  if $CC -o ../bin/$i $i.c; then
+    echo \b\b\b done
+  else
+    echo ""
+  fi
+done
diff --git a/stump/c/cuthead.c b/stump/c/cuthead.c
new file mode 100644 (file)
index 0000000..116bfc3
--- /dev/null
@@ -0,0 +1,38 @@
+/* cuthead.c
+
+   This program simply ignires first line, or if argc == 2, first
+   lines specified by argv[1].
+
+   I am not responsible for any damages, GNU copyright applies.
+*/
+
+#include <stdio.h>
+
+#define MAX_BUF 4096
+
+const char * Usage = "Usage: %s [number-of-lines]\n";
+
+char buf[MAX_BUF];
+
+int main( int argc, char **argv )
+{
+  int first;
+
+  if( argc == 1 ) first = 1;
+  else if( argc == 2 ) {
+    if( (first = atoi( argv[1] ) ) <= 0 ) {
+      fprintf( stderr, Usage, argv[0] );
+      exit( 1 );
+    }
+  } else {
+    fprintf( stderr, Usage, argv[0] );
+    exit( 1 );
+  }
+
+  while( fgets( buf, sizeof( buf ), stdin ) ) {
+    if( first )
+      first--;
+    else
+      fputs( buf, stdout );
+  }
+}
diff --git a/stump/c/isbinary.c b/stump/c/isbinary.c
new file mode 100644 (file)
index 0000000..00678e9
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    isbinary.c
+
+  This program reads an article from standard input and checks if it 
+  is a uuencoded binary. If it is, exits with exit code 0, otherwise 
+  retcode = 1.
+
+  GNU Copyright applies. ichudov@algebra.com
+
+*/
+
+#include <stdio.h>
+
+#define MAX_BUF 16384
+#define MAX_BINARY_LINES 10
+
+char buf[MAX_BUF];
+
+int main( int argc, char *argv[] )
+{
+  int nBinLines = 0, maxNBinLines = 0;
+
+  /* skip header */
+  while( fgets( buf, MAX_BUF, stdin ) ) 
+    if( strlen( buf ) <= 1 ) break;
+
+  while( fgets( buf, MAX_BUF, stdin ) ) {
+    if( strlen( buf ) > 45 /* buf long enough */
+        && (!(strchr( buf, ' ' ) || strchr( buf, '\t' )) /* no spaces */
+           || (buf[0] == 'M') )  /* some uuencoded stuff begins with 'M' */
+      ) { /* likely a uuencoded line */
+      nBinLines++;
+      maxNBinLines = (nBinLines > maxNBinLines) ? nBinLines : maxNBinLines;
+    } else nBinLines = 0;
+  }
+
+  /* more than 10 consecutive 45 char lines with no blank - likely binary */
+  return( maxNBinLines < MAX_BINARY_LINES );
+}
diff --git a/stump/doc/README b/stump/doc/README
new file mode 100644 (file)
index 0000000..822c137
--- /dev/null
@@ -0,0 +1,3 @@
+All STUMP Documentation can be found on the STUMP home page at
+
+               http://www.algebra.com/~ichudov
diff --git a/stump/doc/VERSION b/stump/doc/VERSION
new file mode 100644 (file)
index 0000000..6bc60aa
--- /dev/null
@@ -0,0 +1,32 @@
+VERSION: 2.2
+
+Changes in v 2.2:
+
+       * Improved protection from bounced rejections, to ease work
+         with munged addresses
+       * A single reference to perl, to simplify addressing perl
+
+Changes in v.2.1:
+
+       * Somewhat cleaned it up, added more explanations, improved
+         exchange between mods and the bot.
+       * Cleaned up documentation on the web page
+       * Added capability for multi-line moderator comments
+       * Auto-noack is the mainstream feature that is set up by
+         default.
+       * The bot forms defensive mail headers in order to avoid
+         excessive bounces resulting from munged addresses.
+
+Changes in v.2.0: 
+
+       * Changed all the way human mods interact with the bot;
+       * Made use of PGP optional
+       * Killed ModScape
+       * Set up mail2news gateway.
+
+Cnanges in v.1.1: 
+
+       * Changed send-netscape-submission.pl to enable ModScape for
+         Win95 to work.
+
+
diff --git a/stump/etc/README b/stump/etc/README
new file mode 100644 (file)
index 0000000..19f77fd
--- /dev/null
@@ -0,0 +1,116 @@
+                       STUMP Configuration Directory
+
+This directory contains files that the supporter of the robomoderator
+for your newsgroup (you) is supposed to edit to reflect the
+details of your newsgroup. You will have to edit most of the files
+under this directory to set such parameters as your newsgroup
+name, maximum number of crossposts, specify email addresses of
+your human moderators, and so on.
+
+Below is a short summary of what files you should edit and what they
+are for. Refer to comments in individual files for more details on
+how you should modify them.
+
+I suggest editing the files in the order they are listed here. It 
+is more logical.
+
+==================================================> added-headers
+
+Edit this file and put the header lines that you want to see appended
+to all approved articles. Put there something to the effect that you
+are not responsible for contents of approved posts, put your
+submission and complaint addresses, and if possible provide an
+URL of the Web page with your FAQ and Charter.
+
+==================================================> approval.key.txt
+
+To set this file, you MUST have already created your PGP keys
+needed for moderation. Please refer to the online documentation
+explaining what keys to create and how to do it. Here's how
+to extract the key AFTER you have created it: 
+
+pgp -kxaf csfm-approval-key > $HOME/stump/etc/approval.key.txt
+
+==================================================> rejection-reasons.lst
+
+This file should list exactly the same reasons as mentioned
+in "reject" perl script, BUT in a different format
+
+reason::textual explanation of the reason.
+
+This file is used by the ModScape subsystem.
+
+==================================================> conforms_charter
+
+This shell script allows you to define newsgroup-specific
+checks for submissions. If conforms_charter returns a non-zero
+exit code, the submission will be rejected automatically.
+soc.culture.russian.moderated uses this script to reject
+all binary files.
+
+==================================================> crontab
+
+This file specifies which programs should be run at regular intervals.
+You can skip it if you just started setting up STUMP.
+
+==================================================> messages
+
+This DIRECTORY contains explanations for various reasons for rejection.
+Refer to "reject" script for details on rejection reasons, names and
+files.
+
+==================================================> modack.approved
+
+This shell script is used to send acknowledgments to posters whose messages
+get approved by the robomoderator. Edit this script so that the
+text message is specific to your newsgroups.
+
+==================================================> modack.received
+
+This shell script is used to send acknowledgments to posters whose
+messages were received by the robomoderator. Edit this script so that
+the text message is specific to your newsgroups.
+
+==================================================> modenv
+
+This is the main configuration file for your newsgroup. It has extensive
+instructions. Edit it very carefully and make sure that PATH includes all
+common directories.
+
+==================================================> moderators
+
+This file lists all human moderators and member of moderator
+board for your newsgroup, along with some flags. Please refer to 
+the file itself for more details.
+
+Besides initial setup, you will need to edit this file to support
+moderators going to vacations.
+
+==================================================> mods-message
+
+This shell script is responsible for sending messages for your
+moderator board to the members of said board. You do not really
+need to edit it.
+
+==================================================> mods.sig
+
+Signature for the moderators-only mailing list. It is appended at the
+bottom of every message that is distributed to human moderators.
+Edit it and put whatever you want there.
+
+==================================================> posted_log
+
+This little shell script created a posted log file
+in your home directory. Ignore it.
+
+==================================================> procmail
+
+This DIRECTORY contains some procmail rc files for archiving
+messages. You only need to edit them if you have procmail and
+formail in some non-standard place.
+
+==================================================> procmailrc
+
+This file defines rules for processing incoming mail. You will
+need to edit it extensively if your group relies on different 
+system of aliases than those that I recommend (a bad idea!).
diff --git a/stump/etc/my_rnews b/stump/etc/my_rnews
new file mode 100755 (executable)
index 0000000..8ba5f13
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# use this script instead of standard rnews or inews, if you want better
+# propagation of your articles.
+#
+
+TEMPFILE=$TMP/posting.$$
+
+cat $@ > $TEMPFILE
+
+if /usr/lib/news/rnews -h news.uu.net -S news.uu.net < $TEMPFILE; then
+  echo Rnews successful\!
+else
+  exit 1
+fi
+
+NNTPSERVER=news
+export NNTPSERVER
+
+/usr/lib/news/inews -h < $TEMPFILE &
diff --git a/stump/etc/procmailrc b/stump/etc/procmailrc
new file mode 100644 (file)
index 0000000..2b0b6d7
--- /dev/null
@@ -0,0 +1,135 @@
+# Please check if all the paths in PATH are reachable, remove the ones that
+# are not.
+#
+# NOTE: I use lockfiles extensively (and even excessively) because
+# I do not want to overburden the system. Since I am on a
+# PPP link that is not always on, sometimes large amounts of 
+# submissions come in simultaneously and that may impair
+# performance of the overall system. You do not REALLY need
+# to use these lockfiles otherwise.
+#
+# STUMP USERS: 
+#
+# * Go through this file and replace urcm and
+#   uk.rec.cycling.moderated with appropriate text for
+#   your own group.
+#
+# * Please remove recipes related to user "mkagalen". He is not
+#   likely to bother you.
+#
+# * Replace "ichudov" with the address of the robomod supporter.
+#
+# good luck. igor
+#
+###################################################################
+
+PATH=/bin:/usr/bin:/usr/local/bin:$HOME/stump/bin:$HOME/stump/etc
+MAILDIR=$HOME/Mail     # You'd better make sure it exists
+DEFAULT=$MAILDIR/mbox
+# VERBOSE=ON
+LOGFILE=$MAILDIR/from
+LOCKFILE=$HOME/.lockmail
+
+:0 c
+$MAILDIR/allmail
+
+############################################################ Begin Mailbombing
+:0:
+* ^(From|Sender): .*mkagalen@lynx.dac.neu.edu
+* TOurcm-board
+$MAILDIR/bomb
+
+:0:
+* ^(From|Sender): .*mkagalen@lynx.dac.neu.edu
+* TOurcm-mods
+$MAILDIR/bomb
+
+:0
+* ^From: .*mkagalen@lynx.dac.neu.edu
+* ^Subject: .*Spongiform
+$MAILDIR/bomb
+
+:0:
+* From: "The Filter of mkagalen@lynx"
+$MAILDIR/bomb
+
+#
+# This recipe removes duplicates!
+#
+:0 Wh: msgid.lock
+| formail -D 32768 msgid.cache
+
+
+############################################################ End Mailbombing
+
+# Cabal maillist
+:0 
+* ^From .*uu.net
+* ^To: urcm-board
+| modenv mods-message moderators@isc.org ADVICE
+
+:0
+* ^From .*uu.net
+* ^TOmoderators
+| modenv mods-message moderators@isc.org ADVICE
+
+:0
+* ^From .*isc.org
+* ^TOmoderators
+| modenv mods-message moderators@isc.org ADVICE
+
+
+###################################################################### Standard
+
+:0 
+* ^TOurcm-mods
+| modenv mods-message urcm-mods@algebra.com
+
+:0 
+* ^TOurcm-board
+| modenv mods-message urcm-mods@algebra.com
+
+:0
+* ^TOurcm-noack
+| modenv stump.pl processNoack.pl
+
+:0
+* ^TOsoc-culture-russian-moderated
+| modenv stump.pl submission.pl
+
+:0
+* ^TOuk.rec.cycling.moderated
+| modenv stump.pl submission.pl
+
+
+:0
+* ^TOurcm-approved
+| formail -c | modenv stump.pl acceptFromMod.pl
+
+:0
+* ^TOurcm-rejected
+| modenv stump.pl acceptFromMod.pl
+
+:0 
+* ^TOurcm-admin
+!ichudov
+
+:0
+* ^TOurcm-approval-key
+| modenv send_pgp_key
+
+:0
+* ^Newsgroups: 
+| modenv stump.pl submission.pl
+
+:0 
+* ^FROM_DAEMON
+!ichudov
+
+:0 
+* ^FROM_MAILER
+!ichudov
+
+# Anything that has not been delivered by now is a submission
+:0
+| modenv stump.pl submission.pl
diff --git a/stump/local/README b/stump/local/README
new file mode 100644 (file)
index 0000000..e0cedd9
--- /dev/null
@@ -0,0 +1,580 @@
+PGP Moose
+=========
+by Greg Rose <ggr@usenix.org>
+
+The aim of this software is to monitor the news
+postings of moderators of USENET newsgroups, and to
+automatically cancel forged messages purporting to
+be approved.  This can be extended to the approvals
+of individual users to automatically cancel messages
+that appear without having been authorised by the
+user. This has (obviously) been prompted by the
+recent spammings and other events.
+
+This software and protocol is designed around
+cryptographic signatures.  The protocol is designed
+to allow the use of different signature techniques.
+This implemention assumes the use of PGP signatures,
+but can be easily modified to use others, such as
+the Digital Signature Standard.  PGP was chosen for
+its widespread availability around the world.
+
+PGP, the crux of the cryptographic software, was
+written by Phil Zimmermann <prz@acm.org>, who
+otherwise has nothing to do with this. The
+cryptographic framework was written by Greg Rose
+<ggr@usenix.org>, as were the INN news system
+hooks.
+
+
+Contents:
+--------
+
+How Does It Work?
+The Bits:
+How Do You Register For The Service?
+Handling Multiple Moderated Groups:
+Possible Problems We've Forseen:
+Status:
+Obtaining, installing, configuring:
+It Really Wasn't That Hard.
+License Terms:
+Version:
+
+How Does It Work?
+-----------------
+
+This document is written from the point of view of
+a newsgroup moderator, but individual users could
+also use the facility in analagous ways.
+
+When a moderator wants to protect their group from
+forged/unapproved postings, they should register
+their interest with one or more of the sites running
+PGP Moose, and pick up the submission script. As
+part of this process, the moderator would specify
+one or more PGP public keys that are allowed to
+approve postings.
+
+When a post comes in, and the moderator wishes to
+approve it, they do whatever they would normally
+do before actually using inews (or whatever) to
+post the message. As their last action, they run
+the PGP Moose Approval program "pmapp". This
+inserts a special header which
+looks like this:
+
+X-Auth: PGPMoose V1.0 PGP sci.crypt.research
+       iQBVAgUBL1/Kg2zWcw3p062JAQEYIgH/Xyrz6LaGG+fHaSxoexMECovzkIoADrQx
+       l73IXlUQEIoFl5jnDBBdHVvqTMEPS0118ytYVQZoQrdStuXB9Oc9gQ==
+       =azqs
+
+If there are multiple moderated newsgroups, there
+might be multiple X-Auth: headers, one
+for each group that has requested assistance from
+the PGP Moose daemon.  In this example you can
+see that the authentication carries the name of
+the authenticating program, a protocol version
+number, an identifier of the type of digital
+signature (currently only PGP) and the name of
+the newsgroup in question.  These, as well as the
+From:, Subject: and Message-Id: lines, the list
+of newsgroups, and the non-blank lines of the
+message itself, are used as input to the PGP
+program to generate a signature.
+
+The lines of the message body are preprocessed in
+a way that is meant to render harmless any mangling
+that a typical news system might do to the article.
+The article itself is not changed, only the input
+to the signature generation. If a news system
+subsequently mangles the article in a "norma" way,
+for instance by inserting a ">" in front of a line
+starting with "From", it will still pass the signature
+check.
+
+The list of newsgroups must be handled specially,
+so that an article posted to multiple moderated
+controlled newsgroups can be appropriately
+handled.  See below for a more detailed treatment
+of the issues of posting to multiple moderated
+newsgroups.
+
+The PGP signature is then inserted into the
+X-Auth: header, mostly so that it won't
+interfere with, or be confused with, any signature
+in the body of the message.
+
+Anybody can check whether the message has been
+modified in any significant way, simply by running
+the PGP Moose Approval Checking program "pmcheck".
+More importantly, though, the sites running the PGP
+Moose Checking Daemon will be doing this automatically
+for every posting to the registered newsgroups, or
+from the registered users. And, if a posting fails
+the checks, it may be automatically cancelled, and
+a notification sent to the moderator. (Initially,
+the automatic cancellation will be disabled, since
+it is a pretty powerful sledgehammer, but that is
+the intention anyway.)
+
+This software is made freely available for just
+about any purpose, but I've retained copyright so
+as to keep some semblance of involvement. See the
+last section of this file.
+
+
+The Bits:
+---------
+
+The approval and checking part of the PGP Moose
+consists of a number of Bourne Shell scripts calling
+standard Unix utilities and PGP.  I could have used
+perl more elegantly, but this stuff is marginally
+more widely available.  If there are Unix version
+dependencies, they should be considered to be bugs
+and I'll happily attempt to remove them.
+
+pmapp  usage: pmapp [newsgroup|user] [file]
+       
+       This script takes the not-yet-posted
+       article, specified either by filename or
+       from standard input, and creates a
+       signature for it, which is then inserted
+       in the X-Auth: header. The article,
+       ready for posting, appears on the standard
+       output.
+
+       In the configuration section at the top of
+       the script, the moderator may build in the
+       default name of the newsgroup or user, PGP
+       User Id to be used for the signature, and
+       the corresponding password. This is simply
+       for convenience, since spammers are not so
+       likely to go cracking the computer to get
+       the password, and it is a relatively simple
+       matter to generate a new user if it is,
+       indeed, compromised. For the paranoid, like
+       myself, if the password is not configured
+       into the script it is read from the terminal
+       instead.
+
+pmcheck        usage: pmcheck [newsgroup|user] [article]
+
+       This script takes the article, specified
+       either by filename or from standard
+       input, and checks that the
+       X-Auth: line is something it
+       considers to be correct and that the
+       article has not been tampered with.
+       Pmcheck returns successfully if
+       everything checks out.  Otherwise it will
+       return failure and issue one of a number
+       of error messages, for example:
+
+         Posting for $NEWSGROUP not approved with PGP Moose.
+         Invalid designated signature from $GROUP
+         No public key for signature $GROUP
+         Signature doesn't match $FILE for $GROUP
+         '$SIG' not accepted for $GROUP.
+
+       Anybody can run pmcheck. It behaves slightly
+       differently depending on the existence of
+       a file called (by default)
+       PGP_Moose_accept, and the presence or
+       absence of a newsgroup or user argument.
+       This file, if it exists, should contain
+       lines with a newsgroup name or email address,
+       some whitespace, and the PGP User Id approved
+       by the moderator or user (usually made up
+       specifically for this purpose). Multiple
+       lines for the same newsgroup/user are allowed.
+       For example:
+
+         sci.crypt.research            moderator <ggr@sydney.sterling.com>
+         sci.crypt.research            moderator <pgut01@cs.auckland.ac.nz>
+         ggr@sydney.sterling.com       Greg's News <ggr@sydney.sterling.com>
+
+       If such a file exists, and a specific
+       newsgroup or user is specified, pmcheck is silent
+       if all is well, and issues the last of the
+       error messages above if everything else
+       was all right but the signature was from
+       the wrong person. There must, in this
+       case, be a signature applying to the
+       designated newsgroup or user.
+
+       Without such a file, or if no specific
+       newsgroup or user is given, all the signatures
+       in the article are checked.  In this case
+       it is not considered an error if the signature
+       cannot be checked due to a missing public
+       key.  If each signature is otherwise valid
+       you will get a message like:
+
+         Valid signature from '$SIG'.
+
+       In any case, if there is a problem with a
+       signature mentioned in the PGP_Moose_accept
+       file, it will be reported and an error status
+       will be returned.
+
+pmcanon
+pmnewsgroups
+       These two scripts are used by pmapp and
+       pmcheck to recreate the exact input for
+       the signature, and to extract the list of
+       newsgroups in the header, respectively.
+       More documentation is in their manual
+       pages.
+
+The PGP Moose checking daemon is packaged
+separately, as there would not seem to be a lot
+of value in having too many people running it.
+Accordingly, I was less precise in making it run
+absolutely everywhere. It requires the Korn shell
+or equivalent, and perl, and currently only
+interfaces to INN. I expect it would be easy to
+interface it to CNews, but I don't have one.
+
+pmdaemon
+       Runs pmcheck to check the X-Auth: header
+       for each controlled newsgroup for each
+       article that arrives in an appropriate
+       newsgroup. Mail is sent about any errant
+       articles, and automatic cancellation may
+       be enabled.
+
+pmcancel
+       prepares a cancellation message based on
+       the headers of another message.
+
+When (if) I get a chance, I will create mail
+server scripts that allow moderators who are not
+using Unix to use these facilities.  The first
+allows a moderator to mail a PGP signed copy of
+the article to be posted.  The server will then
+verify that the moderator sent it, and post it
+with a (different but corresponding) approval.
+The second will accept an article and return
+something that you can check the signature on.
+Either way, any moderator will still need PGP.
+
+
+How Do You Register For The Service?
+------------------------------------
+
+Ahhh, this is the hard part. After all, it would
+be pretty undesirable if someone, meaning well,
+took any old body's word for it that some
+important moderated group should start working
+this way, before the moderator was able to start
+approving postings. A great way to hijack a
+newsgroup. Similarly for hijacking some other
+user's postings (tempting though it might be :-).
+
+Another possibility is that someone, having seen
+what the valid signature looks like, simply
+creates a whole new PGP key that happens to have
+the same PGP User ID. Then they can sign and post
+stuff too.
+
+The solution to both of these problems is the
+classical one for public key systems. You need
+either a certifying authority or the PGP Web of
+Trust. We're using the Web of Trust. If you don't
+understand about PGP and the Web of Trust, go away
+now and come back after you really do understand
+it.
+
+For each newsgroup that wants to utilise this
+program, the moderator will have to create a
+special PGP key pair (preferably 512 bits to keep
+the X-Auth: down to two full lines), and sign it. They
+must then establish a path of trust to someone
+who is running the PGP Moose server. It will be
+up to the administrator of that server to make
+sure that only trusted moderators' keys ever get
+into the server's keyring.
+
+THERE CAN BE NO SHORTCUTS TO THIS PROCEDURE.
+Otherwise we are all back where we started.
+
+In the case of an individual user, again you should
+establish this verification path to one of the
+administrators of the PGP Moose service.  Contact
+me (ggr@usenix.org) for the time
+being to mutually figure out how to do this.
+
+
+Handling Multiple Moderated Groups:
+----------------------------------
+
+When I first proposed this tool, I was under the
+impression that postings to multiple moderated
+groups was an abberation that should be stamped
+out. This turns out not to be the case, and
+revisions to support this have been the cause of
+some delay in the deployment of this tool.
+
+When the news system sees that an article has been
+posted to one or more moderated groups, it checks
+for an Approved: header. If the header exists, the
+article is accepted and processed normally,
+otherwise it is mailed to the moderator of the
+first moderated newsgroup mentioned in the
+Newsgroups: header. There seem to be three cases of
+interest.
+
+The trivial case, and the most normal one, is
+that there is only one moderated newsgroup
+mentioned. The moderator approves the posting, and
+it is done.
+
+The next, and probably most important, case, is
+when a moderator wants to cross-post a FAQ to
+their own group, as well as news.answers (for
+example). In this case their approval counts for
+both groups, so they can insert the Approved:
+header and post away. Presumably the other groups
+are not under the control of the PGP Moose Daemon.
+In this case the moderator can just go ahead and
+put in the Approved: header, and save themself
+and pmapp a lot of time. It will be passed right
+through.
+
+The other case is harder to get right. This is when
+the article really is meant to be posted to two (or
+more) unrelated moderated newsgroups.  Now, if the
+first moderated group's moderator approves the
+posting, the other ones never hear about the article,
+at all. If this second group is controlled by the
+PGP Moose an automatic cancel will be generated. So
+it becomes very important for the moderators to do
+what they should have been doing already, namely
+forward the article to the next moderator. This tool
+can't help people who don't use it, but it provides
+some support for those who do.
+
+The approval script checks whether there are any
+moderated newsgroups left that don't have
+X-Auth: headers for them. If there are
+none left, an Approved: header is inserted and the
+article gets posted.  Otherwise, it issues a warning,
+and re-orders the newsgroups header with a newsgroup
+which is moderated but has no X-Auth: line
+at the start.  When the article is posted, the news
+system will forward it to the moderator of the (new)
+first moderated group. If all moderators are sensible,
+and check for moderated newsgroups in this fashion,
+the mess should sort itself out and the last moderator
+will go ahead and post it. A warning nessage to
+the subsequent moderator NOT to change the
+article is also inserted, since such a
+modification would invalidate the previous
+signatures..
+
+To ease this process, a second type of
+X-Auth: header is supported. this has
+the form:
+
+       X-Auth: None ... Newsgroup
+
+The important fact about this is that
+the newsgroup appears last on the line, allowing a
+sort of partial approval, from moderators who
+don't use the PGP Moose.
+
+The Newsgroups: line is split into a sorted list
+of newsgroups for the purpose of generating the
+digital signature. Note that this means that once
+an article has been approved and authenticated by
+one moderator, it cannot be altered in any way by
+a moderator of a subsequent group, including
+altering the set of newsgroups mentioned in the
+Newsgroups: header, the body of the posting, or
+the other headers mentioned above.
+
+
+Possible Problems We've Forseen:
+--------------------------------
+
+If an article is truly mangled e.g. by truncation, it
+will fail the authentication and be cancelled.
+Until it is demonstrated otherwise, this is
+assumed to be a rare and minor problem. When a
+cancel is issued, mail is sent to the moderator of
+the group telling them, and they can tell us if it
+becomes a problem. (In the initial deployment we
+expect that no automatic cancels will actually be
+generated, only the notification mail will be
+sent.)
+
+Currently the signature produced is assumed to be
+a PGP version 2.6 compatible one.
+
+
+Status:
+-------
+
+These scripts are implemented already, or as noted
+above. They are undergoing beta testing and as soon
+as they have settled they will be made available
+via anonymous FTP and posting to comp.sources.misc
+and sci.crypt.research and the moderators' list.
+
+In the meantime, if you want to use the tools, or
+particularly if you want to run a PGP Moose
+checking daemon, contact me (ggr@usenix.org).
+
+
+Obtaining, installing, configuring:
+-----------------------------------
+
+I regret that I don't have a public ftp site, but
+I do have a web page where you can get a
+compressed tar archive of the approval code. It is
+<A HREF=http://www.usenix.org/~ggr/PGPMoose.html>
+off my home page</A>.
+
+It is hard to talk in detail about installation
+and configuration, since many users are not in
+charge of their own news server configuration. In
+my case, I run all of the things out of a
+subdirectory of my home directory. The only
+thing outside this area which must be changed is
+the INN newsfeeds file, if you are running the
+checking daemon. So, get the distribution file as
+above and unpack it whereever you want it to live.
+
+There are configuration sections at the top of
+pmcheck, pmapp and pmdaemon. I like to think that
+they are relatively self-explanatory. One of the
+harder decisions is whether to use a separate
+keyring for PGP Moose applications or not. It is
+very strongly recommended that you do, if you are
+going to run the PGP Moose checking daemon, as
+the keyring files will need to be readable by the
+userid which INN runs under (usually "news").
+Most of these options can also be overridden by
+environment variables or command arguments, so it
+is possible to leave the scripts unmodified and
+simply put a wrapper around them (which is what I
+do).
+
+In the case of pmapp, the newsgroup or user that
+the authentication applies to can be specified on
+the command itself; The PGP user id and password,
+and the Approved: header's contents, can be
+specified by environment variables PMUSER,
+PMPASSWORD and APP, respectively.
+
+For pmcheck, the important one is the name of the
+configuration file specifying which signatures
+are valid for which newsgroups or users.
+
+Pmdaemon runs from INN, and needs some special
+care to set it up. "news" needs access permission
+to the directory and files for PGP Moose, and
+also read permission on the public keyring. Note
+that PGP creates keyrings with only owner
+permissions. The search path is rarely correct,
+and should be set at the top of the pmdaemon
+script. There are also a number of file names and
+mail addresses, but the comments should be clear
+enough.
+
+Lastly, you want to incorporate pmapp in your
+moderation script and possibly your posting
+script. In my case, the last line of my posting
+script basically said
+
+    /usr/local/news/inews -S -h <tempfile
+
+but now it says 
+
+    pmapp <tempfile | /usr/local/news/inews -S -h
+
+To authenticate postings as an individual (as opposed
+to a moderator) I had to take a copy of the
+installed Pnews script, make sure it came earlier
+on my search path than the normal one, and modify
+that. You have to be careful that no extra
+signature files get appended after the pmapp is
+executed. Again, immediately before the "inews"
+call is the right place. I'm not sure whether
+this will work for all versions of news, this is
+not really my field of competence.
+
+
+It Really Wasn't That Hard.
+---------------------------
+
+I wish people had put as much effort into doing
+this as they did into clogging the Moderators'
+mailing list. It wasn't hard.
+
+But you know what was hard? What Phil Zimmermann
+did creating PGP in the first place. Phil is in
+serious legal hassles over PGP, and if you think
+this effort saves you or your company some time
+or money, I'd like you to consider donating some
+of it to Phil's legal defence fund. Write to me
+or Phil's lawyer Phil Dubois <dubois@acm.org>
+regarding how to donate. You can do it over the
+net using PGP! I probably also should thank the
+many people who have worked hard to bring
+encryption back out of the black chambers. Some
+names which directly come to mind are Diffie,
+Hellman, Merkle, Rivest, Shamir, Adelman, Lai,
+Massey, and probably many others.
+
+Share and Enjoy!
+
+
+License Terms:
+-------------
+
+This software is copyrighted by Greg Rose, RoSecure
+Software, and other parties.  The following terms
+apply to all files associated with the software
+unless explicitly disclaimed in individual
+files.
+
+The authors hereby grant permission to use, copy,
+modify, distribute, and license this software and
+its documentation for any purpose, provided that
+existing copyright notices are retained in all
+copies and that this notice is included verbatim
+in any distributions.  No written agreement,
+license, or royalty fee is required for any of
+the authorized uses.  Modifications to this
+software may be copyrighted by their authors and
+need not follow the licensing terms described
+here, provided that the new terms are clearly
+indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE
+LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS
+DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
+IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY
+DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE IS
+PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND
+DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
+MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+
+Version:
+-------
+
+@(#)README     1.6 (PGPMoose) 95/10/21
diff --git a/stump/local/bin/pmapp b/stump/local/bin/pmapp
new file mode 100755 (executable)
index 0000000..76d4449
--- /dev/null
@@ -0,0 +1,301 @@
+#!/bin/sh
+
+# @(#)pmapp     1.18 (PGP Moose) 97/07/10
+# Authorisation script for PGP Moose
+# Written by Greg Rose, RoSecure Software, Copyright C 1995.
+
+# Updated by rec.radio.amateur.moderated moderation team and Tim Skirvin -
+# 21 Nov 2006
+
+# Debugging
+# On most shells the following statements redirect a log of what
+# is happening, AND ALL ERROR MESSAGES, to /tmp/pmdebug. Some shells
+# (notably /bin/sh on Ultrix systems) do not do the redirection.
+#exec 2>/tmp/pmdebug
+#set -x
+
+# Configuration Stuff:
+
+    # Default user / newsgroup. If not set, an argument is mandatory.
+    #DEFAULT_NEWSGROUP=rec.radio.amateur.moderated
+    #DEFAULT_NEWSGROUP=misc.test.moderated
+    #DEFAULT_NEWSGROUP=misc.test
+    #DEFAULT_NEWSGROUP=panix.test
+
+    # If an Approved: line is to be added, this is what it will say.
+    # Can be set in the environment.
+    if [ "x$APP" = "x" ]; then
+        APP="Authorising user <auth@moderator.site.com>"
+    fi
+
+    # If a From: line is to be added, this is what it will say.
+    # Can be set in the environment.
+    if [ "x$FROM" = "x" ]; then
+        if [ "x$LOGNAME" = "x" ]; then
+            LOGNAME=`whoami`
+        fi
+        FROM="`grep \^$LOGNAME: /etc/passwd |
+            awk -F: '{print \$5}'` <$LOGNAME@`hostname`>"
+    fi
+
+    # The PGP user and (optional) password. The password will be read
+    # from the controlling terminal if not specified. If the newsgroup/user
+    # argument is an email address, it is used instead of the configured
+    # PMUSER. The following line provides a default password if it is unset.
+    # This password will NOT be used if an email address is presented as an
+    # argument.
+    # Can be set in the environment.
+    if [ "x$PMUSER" = "x" ]; then
+        PMUSER=pgpmoose
+        DEFAULT_PASSWORD=t
+    fi
+
+    # In the case of a user posting, the name desired on the X-Auth
+    # line might not be the one used by PGP to look up the key.
+    # In this case, set this variable or import it. By default, though,
+    # leave it empty and $PMUSER will be used.
+    # Can be set in the environment.
+    if [ "x$PGPUSER" = "x" ]; then
+        PGPUSER=
+    fi
+
+    # Whether or not to fold long lines.
+    # Can be set in the environment.
+    if [ "x$FOLDLINES" = "x" ]; then
+        FOLDLINES=false         # allowed values are 'true' or 'false'
+    fi
+
+    # Name of the posting host. Must be fully qualified host.domain.
+    # Unless your hostname is not fuly qualified, or you want to use
+    # an alias, you may as well just keep it as it is.
+    HOSTNAME=`hostname`
+
+    # A place to put temp files. These can add up to about twice the
+    # size of the article being posted.
+    TMP=/tmp
+
+    # If a Message-ID is being generated, this will appear in it.
+    DATESTAMP=`date +%Y\%m\%d\%H\%M`  # NO WHITESPACE OR METACHARACTERS
+
+    # Where the active file is on this system, for checking crosspostings
+    # to multiple moderated groups. The check will be disabled if this is
+    # left empty (and Approved: lines will be added if needed).
+    ACTIVE=/usr/local/news/active
+
+    # If the article is crossposted to other moderated groups, a warning
+    # tells you to reorder the newsgroups and submit the article. A copy
+    # of the article is deposited in this file for you to edit.
+    SAVED_ARTICLE=$TMP/article
+
+# End of configuration stuff.
+
+# Be neat and tidy.
+TF=$TMP/pmapp$$
+trap "stty echo </dev/tty >/dev/tty 2>/dev/null; rm -f $TF.?; exit 1" 1 2 3 15
+
+# Check usage, set arguments
+USAGE='echo >&2 "Usage: $0 [newsgroup|user] [article]"; exit 1'
+case $# in
+1)
+    if [ ! -r "$1" ]; then
+        cat >$TF.f
+        FILE=$TF.f
+        FILENAME="on standard input"
+        NEWSGROUP="$1"
+    elif [ ! "$DEFAULT_NEWSGROUP" ]; then
+        echo >&2 "$0: No default user or newsgroup set."
+        eval "$USAGE"
+    else
+        FILE="$1"
+        FILENAME="$1"
+        NEWSGROUP="$DEFAULT_NEWSGROUP"
+        PMPASSWORD="$DEFAULT_PASSWORD"
+    fi
+    ;;
+2)
+    if [ ! -r $2 ]; then
+        echo >&2 "$0: Can't read $2"
+        eval "$USAGE"
+    fi
+    FILE="$2"
+    FILENAME="$2"
+    NEWSGROUP="$1"
+    ;;
+0)
+    if [ ! "$DEFAULT_NEWSGROUP" ]; then
+        echo >&2 "$0: No default user or newsgroup set."
+        eval "$USAGE"
+    fi
+    cat >$TF.f
+    FILE=$TF.f
+    FILENAME="on standard input"
+    NEWSGROUP="$DEFAULT_NEWSGROUP"
+    PMPASSWORD="$DEFAULT_PASSWORD"
+    ;;
+*)
+    eval "$USAGE"
+    ;;
+esac
+
+case "$NEWSGROUP" in
+*@*)
+    USERFLAG=true
+    PMUSER=$NEWSGROUP
+    # PMPASSWORD is left either as imported or null in this case.
+    ;;
+*)
+    USERFLAG=false
+    PMPASSWORD="${PMPASSWORD-$DEFAULT_PASSWORD}"
+    ;;
+esac
+
+# Set the user ID to be given to PGP to look up the key.
+if [ "x$PGPUSER" = "x" ]; then
+    PGPUSER=$PMUSER
+fi
+
+# Split the file into headers and body.
+# Note long lines in the body may be folded.
+sed -n -e '/^ *$/q' -e 'p' $FILE >$TF.h
+{ echo ""; sed -e '1,/^ *$/d' $FILE; } >$TF.b
+if $FOLDLINES; then
+    d='..........'
+    if grep -s "$d$d$d$d$d$d$d$d" $TF.b >/dev/null; then
+        echo >&2 "$0: warning: lines exceed 80 characters, being folded."
+        sed  -e '/.\{80\}/s/.\{79\}/&\
+    /g' $TF.b >$TF.l
+        mv $TF.l $TF.b
+    fi
+fi
+
+if [ ! -s $TF.h ]; then
+    echo "$0: problem with article $FILENAME; header section empty." >&2
+    exit 1
+fi
+
+# If no From: line, add one.
+grep -i -s '^From:' $TF.h >/dev/null ||
+    echo "From: $FROM" >>$TF.h
+
+# If no Message-ID: line, add one.
+grep -i -s '^Message-ID:' $TF.h >/dev/null ||
+    echo "Message-ID: <pgpmoose.$DATESTAMP.$$@$HOSTNAME>" >>$TF.h
+
+# Check for or Provide a Newsgroups: line if none there,
+grep -i -s '^Newsgroups:' $TF.h >/dev/null || \
+    if $USERFLAG; then
+        # that is fine
+        :
+    else
+        echo "Newsgroups: $NEWSGROUP" >>$TF.h
+    fi
+
+# Get the list of newsgroups and check that we are in it!
+pmnewsgroups $TF.h >$TF.n
+$USERFLAG || grep -i -s "^$NEWSGROUP\$" $TF.n >/dev/null || {
+    echo >&2 "$0: Newsgroup $NEWSGROUP not present in article $FILENAME"
+    exit 1
+}
+
+# Read a password if none is configured.
+#if [ ! "$PMPASSWORD" ]; then
+#    stty -echo </dev/tty >/dev/tty 2>/dev/null
+#    echo "Enter PGP passphrase for $PGPUSER:" >/dev/tty
+#    PMPASSWORD=`head -1 </dev/tty`
+#    stty echo </dev/tty >/dev/tty 2>/dev/null
+#fi
+# --passphrase "$PMPASSWORD"
+
+# Compute a signature for the important information.
+#PGPPASSFD=0
+#export PGPPASSFD
+{
+    cat $TF.h $TF.b | pmcanon | tee $TF.m
+} | gpg --detach-sign --textmode --armor --batch --user "$PMUSER_APPROVAL" >$TF.s 2>$TF.e || {
+    echo >&2 "$0: PGP signing failed. PGP output:"
+    cat >&2 $TF.e
+    exit 1
+}
+
+# Add an appropriate X-Auth: header
+{
+    echo "X-Auth: PGPMoose V2.0 PGP $NEWSGROUP"
+    sed -e '1,/^$/d' -e '/END PGP SIGNATURE/d' -e 's/^/ /' $TF.s
+} >>$TF.h
+
+# Since we are in test mode, disable Google archiving
+#grep -i -s '^X-No-Archive:' $TF.h >/dev/null ||
+#    echo "X-No-Archive: yes" >>$TF.h
+
+# Now one of the hardest parts. If there is no Approved: line,
+# check for moderated groups that don't have an X-Auth: line
+# and do something sensible.
+$USERFLAG || grep -i -s '^Approved:' $TF.h >/dev/null || {
+    if [ "$ACTIVE" ]; then
+        # Cut it down to a list of un-X-Auth:ed groups
+        egrep -i '^X-(Auth.*|Approved):' $TF.h \
+            | sed -n -e 's/^.* //p' \
+            | sort \
+            | comm -23 $TF.n - \
+            >$TF.u
+        # Check if any of these are moderated
+        if [ -s $TF.u ]; then
+            badgroup=
+            for i in `cat $TF.u`; do
+                qi=`echo "$i" | sed -e 's/\./\\./g' -e 's/\+/\\+/g' `
+                if grep -i -s "^$qi[    ].*[    ]m$" $ACTIVE >/dev/null; then
+                    echo >&2 "$0: Newsgroup $i is moderated."
+                    badgroup=$i
+                fi
+            done
+            if [ "x$badgroup" != x ]; then
+                cat >&2 <<END
+$0: Other moderated groups appear in newsgroup list.
+    The Authenticated article is saved in $SAVED_ARTICLE.
+    The newsgroups line has been reordered
+    to have it sent to the next moderator in the chain.
+    In future, you can manually add an Approved: line
+    before running this approval script if
+    you are absolutely sure that this is all right.
+END
+                cat >>$TF.h <<END
+X-WARNING-TO-MODERATORS: This article has been processed and
+    accepted using a cryptographic program by the moderator
+    of $NEWSGROUP. Its content must not be changed or it
+    will be automatically cancelled. If you don't like the
+    article or its crossposting, return it to the submitter. 
+    (You can delete this message if you are the last
+    moderator to see it.)
+X-Approved: $APP $NEWSGROUP
+END
+                {
+                    # Make a new set of headers. Most of them are all right.
+                    # Regrettable that this takes so much work, but it
+                    # doesn't happen often, right?
+                    # First pass through all but the Newsgroups:.
+                    sed -n -e '/^[Nn][Ee][Ww][Ss][Gg][Rr][Oo][Uu][Pp][Ss]:/q' \
+                           -e 'p' $TF.h
+                    sed -e '1,/^[Nn][Ee][Ww][Ss][Gg][Rr][Oo][Uu][Pp][Ss]:/d' \
+                        $TF.h \
+                        | sed -n -e '/^[^       ]/,$p'
+                    # Add a new Newsgroups: header
+                    othergroups=`fgrep -v $badgroup $TF.n`
+                    othergroupslist=`echo $othergroups | sed 's/ /,/g'`
+                    echo "Newsgroups: $badgroup,$othergroupslist"
+                    cat $TF.b
+                }
+                rm -f $TF.?
+                exit 0
+            fi
+        fi
+    fi
+    # If not rejected above, we can insert an Approved: header and go for it
+    echo "Approved: $APP" >>$TF.h
+}
+
+# recreate the article on standard output.
+cat $TF.h $TF.b
+
+rm -f $TF.?
+exit 0
+
diff --git a/stump/local/bin/pmcanon b/stump/local/bin/pmcanon
new file mode 100755 (executable)
index 0000000..498dea0
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# @(#)pmcanon  1.9 (PGP Moose) 97/07/10
+# Canonicalisation script for PGP Moose
+# (in other words, it takes a news article and turns it
+# into something we can compute/check a signature on.)
+# Written by Greg Rose, RoSecure Software, Copyright C 1995.
+
+# Be neat and tidy.
+TMP=/tmp
+TF=$TMP/pgpmt$$
+trap "rm -f /$TF.?" 0 1 2 3 15
+
+# Check usage. File must be specified, and result comes out on stdout.
+if [ $# -gt 1 ]; then
+    echo >&2 "usage: $0 [article]"
+    exit 1
+fi
+
+cat $1 >$TF.f
+sed '/^ *$/q' $TF.f >$TF.h
+
+# multiple greps so we can guarantee order
+pmnewsgroups $TF.f
+{
+    grep -i "^From:" "$TF.h"
+    grep -i "^Subject:" "$TF.h"
+    grep -i "^Message-ID:" "$TF.h"
+} | sed -e 's/^[^:]*: *//' -e 's/: */:/g' -e 's/[      ]*$//'
+sed -e '1,/^ *$/d' \
+    -e '/^ *$/d' \
+    -e 's/^--/- --/' \
+    -e 's/^[Ff][Rr][Oo][Mm]/>&/' \
+    -e 's/^[Ss][Uu][Bb][Jj][Ee][Cc][Tt]/>&/' \
+    -e 's/^\.$/../' \
+    -e 's/^\.[^.]/.&/' \
+    -e 's/[    ]*$//' \
+    "$TF.f"
diff --git a/stump/local/bin/pmcheck b/stump/local/bin/pmcheck
new file mode 100755 (executable)
index 0000000..6faeb3f
--- /dev/null
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+# @(#)pmcheck  1.9 (PGP Moose) 96/03/04
+# Authorisation checking script for PGP Moose
+# Written by Greg Rose, RoSecure Software, Copyright C 1995.
+
+# Configuration:
+    # Where to create temporary files.
+    TMP=/tmp
+
+    # Name of the file with valid moderator's/individual's names
+    ACCEPT=PGP_Moose_accept
+
+# End Configuration
+
+# Be neat and tidy.
+TF=$TMP/pmcheck$$
+trap "rm -f $TF.?; exit 1" 1 2 3 15
+
+VERBOSE=false
+# Usage: $0 [newsgroup|user] [filename]
+case $# in
+0)
+    VERBOSE=true
+    NEWSGROUP=any
+    cat >$TF.f
+    FILE=$TF.f
+    FILENAME="standard input"
+    ;;
+1)
+    if [ -f "$1" ]; then
+       VERBOSE=true
+       NEWSGROUP=any
+       FILE="$1"
+       FILENAME="$1"
+    else
+       cat >$TF.f
+       FILE=$TF.f
+       FILENAME="standard input"
+       NEWSGROUP="$1"
+    fi
+    ;;
+2)
+    NEWSGROUP="$1"
+    FILE="$2"
+    FILENAME="$2"
+    ;;
+*)
+    echo >&2 "Usage: $0 [newsgroup|user] [article]"
+    echo >&2 "       newsgroup may be "any" to check all signatures."
+    exit 1
+    ;;
+esac
+
+# Find all the authentication headers we can handle
+grep -i '^X-Auth.*:  *PGPMoose ' $FILE | \
+    sed -e 's/.* //' >$TF.g
+
+# For the time being, simply avoid cancel messages.
+# Note that pmdaemon authenticates them, but you probably don't
+# want to cancel them.
+if grep -i -s '^Control:[      ]*cancel' $FILE >/dev/null; then
+    rm -f $TF.?
+    exit 0
+fi
+
+# The designated newsgroup must be validated.
+if [ "x$NEWSGROUP" != "xany" ]; then
+    grep -i -s "^$NEWSGROUP\$" $TF.g >/dev/null || {
+       echo >&2 "$0: Posting for $NEWSGROUP not approved with PGP Moose."
+       rm -f $TF.?
+       exit 1
+    }
+    # Uncomment this line if you only want to check this one group
+    echo "$NEWSGROUP" >$TF.g
+fi
+
+# Make the document we are going to check signatures on.
+pmcanon $FILE >$TF.m
+    
+# Loop checking all X-Auth: lines required
+echo 0 >$TF.b
+while read GROUP; do
+    # Check whether this is an interesting X-Auth: line.
+    # This is determined by the existence of the $ACCEPT file.
+    # If it exists, only the groups/individuals mentioned are
+    # relevent. Otherwise, check everything in sight, but don't
+    # worry if you can't find a key or the signature doesn't match.
+    # CONTROLLED is 0 if there is a $ACCEPT file and this group/user is
+    # in it.
+    [ -f "$ACCEPT" ] && grep -i -s "^$GROUP[   ]" "$ACCEPT" >/dev/null
+    CONTROLLED=$?
+    if [ "$CONTROLLED" != 0 -a "$NEWSGROUP" != "any" ]; then
+       continue
+    fi
+    # $1      $2       $3   $4  $5
+    # X-Auth: PGPMoose V1.1 PGP sci.crypt.research
+    set -- `grep -i "^X-Auth.*:  *PGPMoose .* $GROUP\$" $FILE`
+
+    # Check for version mismatch, but at the moment we just warn.
+    # It is pretty hard to know just what to do in this case.
+    if [ "$3" != "V2.0" -o "$4" != "PGP" ]; then
+       echo >&2 "$0: warning: version mismatch V2.0 PGP != $3 $4"
+    fi
+
+    # reconstruct the input signature file.
+    cat <<-END_OF_SIG >$TF.s
+       -----BEGIN PGP SIGNATURE-----
+       Version: GnuPG v1.4.5 (NetBSD)
+
+       END_OF_SIG
+    sed -n -e "1,/^[Xx]-[Aa][Uu][Tt][Hh].*:  *PGPMoose .* $GROUP\$/d" \
+       -e '/^ *$/,$d' \
+       -e '/^[^        ]/,$d' \
+       -e 's/^[        ]*//p' \
+       $FILE >>$TF.s
+    cat <<-END_OF_SIG >>$TF.s
+       -----END PGP SIGNATURE-----
+       END_OF_SIG
+
+    # Now we can check the signature.
+    gpg --verify $TF.s $TF.m >$TF.e 2>&1
+    STATUS=$?
+#    cat >&2 $TF.s 
+#    echo ====
+#    cat >&2 $TF.m 
+#    echo ====
+#    cat >&2 $TF.e 
+
+    # If this is a target newsgroup/user, any error is bad news.
+    if [ "$CONTROLLED" = 0 -a $STATUS != 0 ]; then
+       echo >&2 "$0: Invalid designated signature from $GROUP"
+       echo 2 >$TF.b
+       continue
+    fi
+    # There are various understood error codes, not to mention the others...
+    # These codes come from the PGP source, and are probably not immutable.
+    case "$STATUS" in
+    0)
+       # signature checks out... handle that case below
+       ;;
+    11)
+       # Non-existent key
+       if [ "$VERBOSE" = true ]; then
+           echo "No public key for signature $GROUP"
+       fi
+       continue
+       ;;
+    30)
+       # Signature check error
+       $VERBOSE || echo >&2 "Signature doesn't match $FILE for $GROUP"
+       echo 2 >$TF.b
+       continue
+       ;;
+    *)
+       # Some other unknown error. Treat same as Non-existent key.
+       if [ "$VERBOSE" = true ]; then
+           echo "Unknown PGP error, status = $STATUS"
+           cat $TF.e
+       fi
+       continue
+       ;;
+    esac
+
+    SIG=`sed -n 's/gpg: Good signature from "\(.*\)"/\1/p' $TF.e`
+    if [ "x$SIG" = "x" ]; then
+       # this one "can't happen"
+       echo >&2 "$0: MOOSE ERROR: Invalid signature for $GROUP on $FILE."
+    fi
+    if [ "$VERBOSE" = "true" ]; then
+       echo "$0: Verified signature from '$SIG'."
+    fi
+
+    # Finally, was it signed by the right person?
+    if [ "$CONTROLLED" = 0 ]; then
+       grep -i -s "^$GROUP[     ]*$SIG\$" "$ACCEPT" >/dev/null || {
+           echo >&2 "$0: '$SIG' not accepted for $GROUP."
+           echo 2 >$TF.b
+       }
+    fi
+done <$TF.g
+
+BADSIG=`cat $TF.b`
+rm -f $TF.?
+exit $BADSIG
diff --git a/stump/local/bin/pmnewsgroups b/stump/local/bin/pmnewsgroups
new file mode 100755 (executable)
index 0000000..e421cbb
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# @(#)pmnewsgroups     1.3 (PGP Moose) 95/11/13
+# Return sorted list of newsgroups in message
+sed -n -e '/^[Nn][Ee][Ww][Ss][Gg][Rr][Oo][Uu][Pp][Ss]:/,/^[^   ]/p' \
+       -e '/^[         ]*$/q' \
+       $* \
+    | sed -e 's/^[Nn][Ee][Ww][Ss][Gg][Rr][Oo][Uu][Pp][Ss]:[    ]*/ /' \
+       -e '/^[^        ]/d' \
+       -e 's/^[        ]*//' \
+       -e 's/[         ]*$//' \
+       -e 's/,[        ]*/\
+/g' \
+    | tr "[A-Z]" "[a-z]" \
+    | sort \
+    | sed -e '/^[      ]*$/d'
diff --git a/stump/local/doc/PGPMoose.README b/stump/local/doc/PGPMoose.README
new file mode 100644 (file)
index 0000000..61b09f0
--- /dev/null
@@ -0,0 +1,580 @@
+PGP Moose
+=========
+by Greg Rose <Greg_Rose@sydney.sterling.com>
+
+The aim of this software is to monitor the news
+postings of moderators of USENET newsgroups, and to
+automatically cancel forged messages purporting to
+be approved.  This can be extended to the approvals
+of individual users to automatically cancel messages
+that appear without having been authorised by the
+user. This has (obviously) been prompted by the
+recent spammings and other events.
+
+This software and protocol is designed around
+cryptographic signatures.  The protocol is designed
+to allow the use of different signature techniques.
+This implemention assumes the use of PGP signatures,
+but can be easily modified to use others, such as
+the Digital Signature Standard.  PGP was chosen for
+its widespread availability around the world.
+
+PGP, the crux of the cryptographic software, was
+written by Phil Zimmermann <prz@acm.org>, who
+otherwise has nothing to do with this. The
+cryptographic framework was written by Greg Rose
+<Greg_Rose@sydney.sterling.com>, as were the INN
+news system hooks.
+
+
+Contents:
+--------
+
+How Does It Work?
+The Bits:
+How Do You Register For The Service?
+Handling Multiple Moderated Groups:
+Possible Problems We've Forseen:
+Status:
+Obtaining, installing, configuring:
+It Really Wasn't That Hard.
+License Terms:
+Version:
+
+How Does It Work?
+-----------------
+
+This document is written from the point of view of
+a newsgroup moderator, but individual users could
+also use the facility in analagous ways.
+
+When a moderator wants to protect their group from
+forged/unapproved postings, they should register
+their interest with one or more of the sites running
+PGP Moose, and pick up the submission script. As
+part of this process, the moderator would specify
+one or more PGP public keys that are allowed to
+approve postings.
+
+When a post comes in, and the moderator wishes to
+approve it, they do whatever they would normally
+do before actually using inews (or whatever) to
+post the message. As their last action, they run
+the PGP Moose Approval program "pmapp". This
+inserts a special header which
+looks like this:
+
+X-Auth: PGPMoose V1.0 PGP sci.crypt.research
+       iQBVAgUBL1/Kg2zWcw3p062JAQEYIgH/Xyrz6LaGG+fHaSxoexMECovzkIoADrQx
+       l73IXlUQEIoFl5jnDBBdHVvqTMEPS0118ytYVQZoQrdStuXB9Oc9gQ==
+       =azqs
+
+If there are multiple moderated newsgroups, there
+might be multiple X-Auth: headers, one
+for each group that has requested assistance from
+the PGP Moose daemon.  In this example you can
+see that the authentication carries the name of
+the authenticating program, a protocol version
+number, an identifier of the type of digital
+signature (currently only PGP) and the name of
+the newsgroup in question.  These, as well as the
+From:, Subject: and Message-Id: lines, the list
+of newsgroups, and the non-blank lines of the
+message itself, are used as input to the PGP
+program to generate a signature.
+
+The lines of the message body are preprocessed in
+a way that is meant to render harmless any mangling
+that a typical news system might do to the article.
+The article itself is not changed, only the input
+to the signature generation. If a news system
+subsequently mangles the article in a "norma" way,
+for instance by inserting a ">" in front of a line
+starting with "From", it will still pass the signature
+check.
+
+The list of newsgroups must be handled specially,
+so that an article posted to multiple moderated
+controlled newsgroups can be appropriately
+handled.  See below for a more detailed treatment
+of the issues of posting to multiple moderated
+newsgroups.
+
+The PGP signature is then inserted into the
+X-Auth: header, mostly so that it won't
+interfere with, or be confused with, any signature
+in the body of the message.
+
+Anybody can check whether the message has been
+modified in any significant way, simply by running
+the PGP Moose Approval Checking program "pmcheck".
+More importantly, though, the sites running the PGP
+Moose Checking Daemon will be doing this automatically
+for every posting to the registered newsgroups, or
+from the registered users. And, if a posting fails
+the checks, it may be automatically cancelled, and
+a notification sent to the moderator. (Initially,
+the automatic cancellation will be disabled, since
+it is a pretty powerful sledgehammer, but that is
+the intention anyway.)
+
+This software is made freely available for just
+about any purpose, but I've retained copyright so
+as to keep some semblance of involvement. See the
+last section of this file.
+
+
+The Bits:
+---------
+
+The approval and checking part of the PGP Moose
+consists of a number of Bourne Shell scripts calling
+standard Unix utilities and PGP.  I could have used
+perl more elegantly, but this stuff is marginally
+more widely available.  If there are Unix version
+dependencies, they should be considered to be bugs
+and I'll happily attempt to remove them.
+
+pmapp  usage: pmapp [newsgroup|user] [file]
+       
+       This script takes the not-yet-posted
+       article, specified either by filename or
+       from standard input, and creates a
+       signature for it, which is then inserted
+       in the X-Auth: header. The article,
+       ready for posting, appears on the standard
+       output.
+
+       In the configuration section at the top of
+       the script, the moderator may build in the
+       default name of the newsgroup or user, PGP
+       User Id to be used for the signature, and
+       the corresponding password. This is simply
+       for convenience, since spammers are not so
+       likely to go cracking the computer to get
+       the password, and it is a relatively simple
+       matter to generate a new user if it is,
+       indeed, compromised. For the paranoid, like
+       myself, if the password is not configured
+       into the script it is read from the terminal
+       instead.
+
+pmcheck        usage: pmcheck [newsgroup|user] [article]
+
+       This script takes the article, specified
+       either by filename or from standard
+       input, and checks that the
+       X-Auth: line is something it
+       considers to be correct and that the
+       article has not been tampered with.
+       Pmcheck returns successfully if
+       everything checks out.  Otherwise it will
+       return failure and issue one of a number
+       of error messages, for example:
+
+         Posting for $NEWSGROUP not approved with PGP Moose.
+         Invalid designated signature from $GROUP
+         No public key for signature $GROUP
+         Signature doesn't match $FILE for $GROUP
+         '$SIG' not accepted for $GROUP.
+
+       Anybody can run pmcheck. It behaves slightly
+       differently depending on the existence of
+       a file called (by default)
+       PGP_Moose_accept, and the presence or
+       absence of a newsgroup or user argument.
+       This file, if it exists, should contain
+       lines with a newsgroup name or email address,
+       some whitespace, and the PGP User Id approved
+       by the moderator or user (usually made up
+       specifically for this purpose). Multiple
+       lines for the same newsgroup/user are allowed.
+       For example:
+
+         sci.crypt.research            moderator <ggr@sydney.sterling.com>
+         sci.crypt.research            moderator <pgut01@cs.auckland.ac.nz>
+         ggr@sydney.sterling.com       Greg's News <ggr@sydney.sterling.com>
+
+       If such a file exists, and a specific
+       newsgroup or user is specified, pmcheck is silent
+       if all is well, and issues the last of the
+       error messages above if everything else
+       was all right but the signature was from
+       the wrong person. There must, in this
+       case, be a signature applying to the
+       designated newsgroup or user.
+
+       Without such a file, or if no specific
+       newsgroup or user is given, all the signatures
+       in the article are checked.  In this case
+       it is not considered an error if the signature
+       cannot be checked due to a missing public
+       key.  If each signature is otherwise valid
+       you will get a message like:
+
+         Valid signature from '$SIG'.
+
+       In any case, if there is a problem with a
+       signature mentioned in the PGP_Moose_accept
+       file, it will be reported and an error status
+       will be returned.
+
+pmcanon
+pmnewsgroups
+       These two scripts are used by pmapp and
+       pmcheck to recreate the exact input for
+       the signature, and to extract the list of
+       newsgroups in the header, respectively.
+       More documentation is in their manual
+       pages.
+
+The PGP Moose checking daemon is packaged
+separately, as there would not seem to be a lot
+of value in having too many people running it.
+Accordingly, I was less precise in making it run
+absolutely everywhere. It requires the Korn shell
+or equivalent, and perl, and currently only
+interfaces to INN. I expect it would be easy to
+interface it to CNews, but I don't have one.
+
+pmdaemon
+       Runs pmcheck to check the X-Auth: header
+       for each controlled newsgroup for each
+       article that arrives in an appropriate
+       newsgroup. Mail is sent about any errant
+       articles, and automatic cancellation may
+       be enabled.
+
+pmcancel
+       prepares a cancellation message based on
+       the headers of another message.
+
+When (if) I get a chance, I will create mail
+server scripts that allow moderators who are not
+using Unix to use these facilities.  The first
+allows a moderator to mail a PGP signed copy of
+the article to be posted.  The server will then
+verify that the moderator sent it, and post it
+with a (different but corresponding) approval.
+The second will accept an article and return
+something that you can check the signature on.
+Either way, any moderator will still need PGP.
+
+
+How Do You Register For The Service?
+------------------------------------
+
+Ahhh, this is the hard part. After all, it would
+be pretty undesirable if someone, meaning well,
+took any old body's word for it that some
+important moderated group should start working
+this way, before the moderator was able to start
+approving postings. A great way to hijack a
+newsgroup. Similarly for hijacking some other
+user's postings (tempting though it might be :-).
+
+Another possibility is that someone, having seen
+what the valid signature looks like, simply
+creates a whole new PGP key that happens to have
+the same PGP User ID. Then they can sign and post
+stuff too.
+
+The solution to both of these problems is the
+classical one for public key systems. You need
+either a certifying authority or the PGP Web of
+Trust. We're using the Web of Trust. If you don't
+understand about PGP and the Web of Trust, go away
+now and come back after you really do understand
+it.
+
+For each newsgroup that wants to utilise this
+program, the moderator will have to create a
+special PGP key pair (preferably 512 bits to keep
+the X-Auth: down to two full lines), and sign it. They
+must then establish a path of trust to someone
+who is running the PGP Moose server. It will be
+up to the administrator of that server to make
+sure that only trusted moderators' keys ever get
+into the server's keyring.
+
+THERE CAN BE NO SHORTCUTS TO THIS PROCEDURE.
+Otherwise we are all back where we started.
+
+In the case of an individual user, again you should
+establish this verification path to one of the
+administrators of the PGP Moose service.  Contact
+me (Greg_Rose@sydney.sterling.com) for the time
+being to mutually figure out how to do this.
+
+
+Handling Multiple Moderated Groups:
+----------------------------------
+
+When I first proposed this tool, I was under the
+impression that postings to multiple moderated
+groups was an abberation that should be stamped
+out. This turns out not to be the case, and
+revisions to support this have been the cause of
+some delay in the deployment of this tool.
+
+When the news system sees that an article has been
+posted to one or more moderated groups, it checks
+for an Approved: header. If the header exists, the
+article is accepted and processed normally,
+otherwise it is mailed to the moderator of the
+first moderated newsgroup mentioned in the
+Newsgroups: header. There seem to be three cases of
+interest.
+
+The trivial case, and the most normal one, is
+that there is only one moderated newsgroup
+mentioned. The moderator approves the posting, and
+it is done.
+
+The next, and probably most important, case, is
+when a moderator wants to cross-post a FAQ to
+their own group, as well as news.answers (for
+example). In this case their approval counts for
+both groups, so they can insert the Approved:
+header and post away. Presumably the other groups
+are not under the control of the PGP Moose Daemon.
+In this case the moderator can just go ahead and
+put in the Approved: header, and save themself
+and pmapp a lot of time. It will be passed right
+through.
+
+The other case is harder to get right. This is when
+the article really is meant to be posted to two (or
+more) unrelated moderated newsgroups.  Now, if the
+first moderated group's moderator approves the
+posting, the other ones never hear about the article,
+at all. If this second group is controlled by the
+PGP Moose an automatic cancel will be generated. So
+it becomes very important for the moderators to do
+what they should have been doing already, namely
+forward the article to the next moderator. This tool
+can't help people who don't use it, but it provides
+some support for those who do.
+
+The approval script checks whether there are any
+moderated newsgroups left that don't have
+X-Auth: headers for them. If there are
+none left, an Approved: header is inserted and the
+article gets posted.  Otherwise, it issues a warning,
+and re-orders the newsgroups header with a newsgroup
+which is moderated but has no X-Auth: line
+at the start.  When the article is posted, the news
+system will forward it to the moderator of the (new)
+first moderated group. If all moderators are sensible,
+and check for moderated newsgroups in this fashion,
+the mess should sort itself out and the last moderator
+will go ahead and post it. A warning nessage to
+the subsequent moderator NOT to change the
+article is also inserted, since such a
+modification would invalidate the previous
+signatures..
+
+To ease this process, a second type of
+X-Auth: header is supported. this has
+the form:
+
+       X-Auth: None ... Newsgroup
+
+The important fact about this is that
+the newsgroup appears last on the line, allowing a
+sort of partial approval, from moderators who
+don't use the PGP Moose.
+
+The Newsgroups: line is split into a sorted list
+of newsgroups for the purpose of generating the
+digital signature. Note that this means that once
+an article has been approved and authenticated by
+one moderator, it cannot be altered in any way by
+a moderator of a subsequent group, including
+altering the set of newsgroups mentioned in the
+Newsgroups: header, the body of the posting, or
+the other headers mentioned above.
+
+
+Possible Problems We've Forseen:
+--------------------------------
+
+If an article is truly mangled e.g. by truncation, it
+will fail the authentication and be cancelled.
+Until it is demonstrated otherwise, this is
+assumed to be a rare and minor problem. When a
+cancel is issued, mail is sent to the moderator of
+the group telling them, and they can tell us if it
+becomes a problem. (In the initial deployment we
+expect that no automatic cancels will actually be
+generated, only the notification mail will be
+sent.)
+
+Currently the signature produced is assumed to be
+a PGP version 2.6 compatible one.
+
+
+Status:
+-------
+
+These scripts are implemented already, or as noted
+above. They are undergoing beta testing and as soon
+as they have settled they will be made available
+via anonymous FTP and posting to comp.sources.misc
+and sci.crypt.research and the moderators' list.
+
+In the meantime, if you want to use the tools, or
+particularly if you want to run a PGP Moose
+checking daemon, contact me (Greg_Rose@sydney.sterling.com).
+
+
+Obtaining, installing, configuring:
+-----------------------------------
+
+I regret that I don't have a public ftp site, but
+I do have a web page where you can get a
+compressed tar archive of the approval code. It is
+<A HREF=http://www.sydney.sterling.com:8080/~ggr/PGPMoose.html>
+off my home page</A>.
+
+It is hard to talk in detail about installation
+and configuration, since many users are not in
+charge of their own news server configuration. In
+my case, I run all of the things out of a
+subdirectory of my home directory. The only
+thing outside this area which must be changed is
+the INN newsfeeds file, if you are running the
+checking daemon. So, get the distribution file as
+above and unpack it whereever you want it to live.
+
+There are configuration sections at the top of
+pmcheck, pmapp and pmdaemon. I like to think that
+they are relatively self-explanatory. One of the
+harder decisions is whether to use a separate
+keyring for PGP Moose applications or not. It is
+very strongly recommended that you do, if you are
+going to run the PGP Moose checking daemon, as
+the keyring files will need to be readable by the
+userid which INN runs under (usually "news").
+Most of these options can also be overridden by
+environment variables or command arguments, so it
+is possible to leave the scripts unmodified and
+simply put a wrapper around them (which is what I
+do).
+
+In the case of pmapp, the newsgroup or user that
+the authentication applies to can be specified on
+the command itself; The PGP user id and password,
+and the Approved: header's contents, can be
+specified by environment variables PMUSER,
+PMPASSWORD and APP, respectively.
+
+For pmcheck, the important one is the name of the
+configuration file specifying which signatures
+are valid for which newsgroups or users.
+
+Pmdaemon runs from INN, and needs some special
+care to set it up. "news" needs access permission
+to the directory and files for PGP Moose, and
+also read permission on the public keyring. Note
+that PGP creates keyrings with only owner
+permissions. The search path is rarely correct,
+and should be set at the top of the pmdaemon
+script. There are also a number of file names and
+mail addresses, but the comments should be clear
+enough.
+
+Lastly, you want to incorporate pmapp in your
+moderation script and possibly your posting
+script. In my case, the last line of my posting
+script basically said
+
+    /usr/local/news/inews -S -h <tempfile
+
+but now it says 
+
+    pmapp <tempfile | /usr/local/news/inews -S -h
+
+To authenticate postings as an individual (as opposed
+to a moderator) I had to take a copy of the
+installed Pnews script, make sure it came earlier
+on my search path than the normal one, and modify
+that. You have to be careful that no extra
+signature files get appended after the pmapp is
+executed. Again, immediately before the "inews"
+call is the right place. I'm not sure whether
+this will work for all versions of news, this is
+not really my field of competence.
+
+
+It Really Wasn't That Hard.
+---------------------------
+
+I wish people had put as much effort into doing
+this as they did into clogging the Moderators'
+mailing list. It wasn't hard.
+
+But you know what was hard? What Phil Zimmermann
+did creating PGP in the first place. Phil is in
+serious legal hassles over PGP, and if you think
+this effort saves you or your company some time
+or money, I'd like you to consider donating some
+of it to Phil's legal defence fund. Write to me
+or Phil's lawyer Phil Dubois <dubois@acm.org>
+regarding how to donate. You can do it over the
+net using PGP! I probably also should thank the
+many people who have worked hard to bring
+encryption back out of the black chambers. Some
+names which directly come to mind are Diffie,
+Hellman, Merkle, Rivest, Shamir, Adelman, Lai,
+Massey, and probably many others.
+
+Share and Enjoy!
+
+
+License Terms:
+-------------
+
+This software is copyrighted by Greg Rose, RoSecure
+Software, and other parties.  The following terms
+apply to all files associated with the software
+unless explicitly disclaimed in individual
+files.
+
+The authors hereby grant permission to use, copy,
+modify, distribute, and license this software and
+its documentation for any purpose, provided that
+existing copyright notices are retained in all
+copies and that this notice is included verbatim
+in any distributions.  No written agreement,
+license, or royalty fee is required for any of
+the authorized uses.  Modifications to this
+software may be copyrighted by their authors and
+need not follow the licensing terms described
+here, provided that the new terms are clearly
+indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE
+LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS
+DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
+IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY
+DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE IS
+PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND
+DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
+MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+
+Version:
+-------
+
+@(#)README     1.6 (PGPMoose) 95/10/21
diff --git a/stump/local/man/man1/pmapp.1 b/stump/local/man/man1/pmapp.1
new file mode 100644 (file)
index 0000000..0828328
--- /dev/null
@@ -0,0 +1,90 @@
+.TH PMAPP 1 "PGP Moose"
+.\"@(#)pmapp.1 1.4 (PGPMoose) 95/10/21
+.SH NAME
+pmapp \- insert a verifiable signature into a
+news article.
+.SH SYNOPSIS
+.B pmapp
+.I [newsgroups|user] [file] 
+.SH DESCRIPTION
+.I Pmapp
+accepts an article from the named
+.IR file
+(or standard input if not specified),
+computes a digital signature from the important
+part of the contents, and inserts a special
+header into the article which enables subsequent
+checking of the signature. The amended article is
+produced on standard output, suitable for
+injection into the news system.
+.LP
+The signature is created using the defaults for
+the user name (and optionally password) built
+into the script when a newsgroup is specified. 
+This is the most common case for a newsgroup
+moderator who will use a customised version of
+the script. These defaults can be overridden with
+environment variables \f3PMUSER\fP and
+\f3PMPASSWORD\fP.
+.LP
+In any case, whenever the
+password is the empty string, a prompt is
+written to the controlling terminal and a
+password is read from it.
+.LP
+When a user is specified (by way of an email
+address containing an "\fB@\fP") this is used to
+select the signing user, and a password will be
+requested.
+.LP
+When the signature is being generated for a
+newsgroup, it is assumed that the newsgroup is
+moderated and the invoking user is (one of) the
+moderator(s).
+The simple case is that there is only one
+newsgroup specified; an Approved: line is added
+as well as the X-Auth: line, and the
+article is ready for posting.
+.LP
+\f2Pmapp\fP goes to some trouble to
+handle the case of multiple moderated newsgroups
+in the Newsgroups: line. The first, and probably
+most important, case, is
+when a moderator wants to cross-post a FAQ to
+their own group, as well as news.answers (for
+example). In this case their approval counts for
+both groups, so they can insert the Approved:
+header and post away. Presumably the other groups
+are not under the contol of the PGP Moose Daemon.
+The moderator can just go ahead and
+put in the Approved: header, and save themself
+and pmapp a lot of time. It will be passed right
+through.
+.LP
+The other case is harder to get right. This is when
+the article really is meant to be posted to two (or
+more) unrelated moderated newsgroups. The approval
+script checks whether there are any moderated
+newsgroups left that don't have X-Auth:
+headers for them. If there are none left, an Approved:
+header is inserted and the article gets posted.
+Otherwise, it issues a warning, and re-orders the
+newsgroups header with a newsgroup which is moderated
+but has no X-Auth:  line at the start.
+When the article is posted, the news system will
+forward it to the moderator of the (new) first
+moderated group. If all moderators are sensible,
+and check for moderated newsgroups in this fashion,
+the mess should sort itself out and the last moderator
+will go ahead and post it. A warning message to the
+subsequent moderator NOT to change the article is
+also inserted.
+.\".SH FILES
+.SH SEE ALSO
+.IR pmcheck (1),
+.IR pmcanon (1)
+for a description of the fields which go into the
+signature calculation,
+the PGP User's Manual.
+.SH AUTHOR
+Greg Rose, RoSecure Software.
diff --git a/stump/local/man/man1/pmcanon.1 b/stump/local/man/man1/pmcanon.1
new file mode 100644 (file)
index 0000000..f80a566
--- /dev/null
@@ -0,0 +1,44 @@
+.TH PMCANON 1 "PGP Moose"
+.\"@(#)pmcanon.1       1.3 (PGPMoose) 95/10/21
+.SH NAME
+pmcanon \- put a news article in canonical format
+for signature checking
+.SH SYNOPSIS
+.BI pmcanon [file] 
+.SH DESCRIPTION
+.I Pmcanon
+accepts an article from the named
+.I file
+(or standard input if not specified), selects
+certain of the header fields,
+and performs certain transformations intended to
+defeat unintentional mangling by news transport
+programs. The resulting article is written to
+standard output, and is suitable to have a PGP
+signature generated or checked for it.
+.LP
+The output produced consists (in order) of a sorted list,
+one per line, of newsgroups; the From:, Subject:,
+and Message-ID: lines, and the body of the
+article (everything after the first empty line).
+Other header lines are discarded.
+.LP
+The body is further processed by deleting all
+blank lines, deleting trailing whitespace on
+lines, prefixing lines beginning with "--",
+"From", "Subject", and a single "." with "-\ ",
+">", ">" and ".", respectively. These
+modifications are safe, in that an article which
+has already had such modifications will fail to
+meet the criterion to have them happen again.
+Thus what was used as input when a signature was
+generated should still be the same when checked
+for validity, even if some of these changes have
+happened along the delivery path.
+.\".SH FILES
+.SH SEE ALSO
+.IR pmapp (1),
+.IR pmcheck (1),
+PGP manual.
+.SH AUTHOR
+Greg Rose, RoSecure Software.
diff --git a/stump/local/man/man1/pmcheck.1 b/stump/local/man/man1/pmcheck.1
new file mode 100644 (file)
index 0000000..7a70701
--- /dev/null
@@ -0,0 +1,83 @@
+.TH PMCHECK 1 "PGP Moose"
+.\"@(#)pmcheck.1       1.5 (PGPMoose) 95/11/15
+.SH NAME
+pmcheck \- check veracity and applicability of
+signatures in news articles.
+.SH SYNOPSIS
+.B pmcheck
+.I [newsgroups|user] [file] 
+.SH DESCRIPTION
+.I Pmcheck
+accepts an article from the named
+.IR file
+(or standard input if not specified),
+and performs certain checks against digital
+signatures present in X-Auth: headers
+in the news articles. There are two common modes
+of use
+of \f2pmcheck\fP, and these are described
+separately for simplicity, even though there is
+considerable ability to mix-and-match.
+.LP
+The first use is when a person is reading news,
+and sees an article and wishes to check whether
+the article is an approved posting to a moderated
+newsgroup, or an approved posting from a
+particular individual user. Piping the article
+through \f2pmcheck\fP will give a list of valid
+signatures (or signatures which couldn't be
+checked because corresponding PGP public keys
+were unavailable), and of course generate error
+messages for invalid signatures, which indicate
+either forged or altered articles. Any alteration
+might have been intentional, but bear in mind the
+possibility that an alteration could have been an
+artifact of the news system, despite precautions
+against this.
+.LP
+The second use, and the reason for the existence
+of the PGP Moose system, is when an article is
+automatically checked upon receipt by a
+designated news hub. In this case, a moderated
+newsgroup or user name (represented by an
+electronic mail address) will be specified, and
+it is considered an error if there is no
+corresponding X-Auth: header, or if for
+any reason it doesn't check out. Furthermore, there
+can be a configured file which lists pairs of
+newsgroup/user names, and corresponding PGP user
+IDs who are allowed to authorise such postings.
+Even a valid signature from an individual who is
+not listed in this file will be considered an
+error. All X-Auth: headers will be
+checked if their newsgroup/user name appears in
+the checking file, the only way in which the
+argument is special is that such a header for
+that newsgroup or user \f3must\fP appear.
+The intention is that any article which fails
+this authentication process will be reported to
+the user or newsgroup moderator(s), and might be
+automatically cancelled. This is to react quickly
+to spamming attacks on moderated newsgroups.
+.SH EXIT STATUS
+.I Pmcheck
+returns an exit status of 0 if everything is all right, and non-zero otherwise.
+In particular, an exit status of 1 means that the article was not
+approved with the PGP Moose when it should have been, and a status of
+2 is returned for all other authentication problems.
+.SH SEE ALSO
+.IR pmapp (1),
+.IR pmcanon (1)
+for a description of the fields which go into the
+signature calculation,
+the PGP User's Manual,
+the PGP Moose README file for an understanding of
+how it all hangs together.
+.SH BUGS
+Currently \f3pmcheck\fP always allows cancel
+messages to pass, despite the fact that
+\f3pmdaemon\fP always authenticates them. The
+potential consequences of an automated cancellation-war were
+simply too horrible to contemplate.
+.SH AUTHOR
+Greg Rose, RoSecure Software.
diff --git a/stump/local/man/man1/pmnewsgroups.1 b/stump/local/man/man1/pmnewsgroups.1
new file mode 100644 (file)
index 0000000..e25e32c
--- /dev/null
@@ -0,0 +1,22 @@
+.TH PMNEWSGROUPS 1 "PGP Moose"
+.\"@(#)pmnewsgroups.1  1.3 (PGPMoose) 95/10/21
+.SH NAME
+pmnewsgroups \- extract a list of all newsgroups
+from an article's headers.
+.SH SYNOPSIS
+.BI pmnewsgroups [file] 
+.SH DESCRIPTION
+.I Pmnewsgroups
+accepts an article from the named
+.I file
+(or standard input if not specified), selects
+the header field, Newsgroups:,
+and writes to standard output a sorted list of
+the newsgroups present, one per line.
+.\".SH FILES
+.SH SEE ALSO
+.IR pmapp (1),
+.IR pmcheck (1),
+.IR pmcanon (1),
+.SH AUTHOR
+Greg Rose, RoSecure Software.
diff --git a/stump/local/src/PGPMoose.shar.gz b/stump/local/src/PGPMoose.shar.gz
new file mode 100644 (file)
index 0000000..a2bdc4d
Binary files /dev/null and b/stump/local/src/PGPMoose.shar.gz differ
diff --git a/webstump/LICENSE b/webstump/LICENSE
new file mode 100644 (file)
index 0000000..5107f64
--- /dev/null
@@ -0,0 +1,355 @@
+What this legalese means is basically two things:
+
+1) There is NO WARRANTY
+2) You are free to copy and modify this program as long as you do 
+not impose more restrictive terms on its copying.
+
+igor
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.  59 Temple Place -
+Suite 330, Boston, MA  02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom
+to share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and
+to any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Library General Public
+License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it in
+new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And
+you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on,
+we want its recipients to know that what they have is not the original,
+so that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it, either
+verbatim or with modifications and/or translated into another language.
+(Hereinafter, translation is included without limitation in the term
+"modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of running
+the Program is not restricted, and the output from the Program is covered
+only if its contents constitute a work based on the Program (independent
+of having been made by running the Program). Whether that is true depends
+on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously
+and appropriately publish on each copy an appropriate copyright notice
+and disclaimer of warranty; keep intact all the notices that refer to
+this License and to the absence of any warranty; and give any other
+recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of
+it, thus forming a work based on the Program, and copy and distribute
+such modifications or work under the terms of Section 1 above, provided
+that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any part
+    thereof, to be licensed as a whole at no charge to all third parties
+    under the terms of this License.
+
+    c) If the modified program normally reads commands interactively when
+    run, you must cause it, when started running for such interactive
+    use in the most ordinary way, to print or display an announcement
+    including an appropriate copyright notice and a notice that there
+    is no warranty (or else, saying that you provide a warranty) and
+    that users may redistribute the program under these conditions, and
+    telling the user how to view a copy of this License. (Exception: if
+    the Program itself is interactive but does not normally print such
+    an announcement, your work based on the Program is not required to
+    print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be
+reasonably considered independent and separate works in themselves,
+then this License, and its terms, do not apply to those sections when
+you distribute them as separate works. But when you distribute the same
+sections as part of a whole which is a work based on the Program, the
+distribution of the whole must be on the terms of this License, whose
+permissions for other licensees extend to the entire whole, and thus to
+each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works
+based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the
+scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections 1
+    and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years,
+    to give any third party, for a charge no more than your cost of
+    physically performing source distribution, a complete machine-readable
+    copy of the corresponding source code, to be distributed under the
+    terms of Sections 1 and 2 above on a medium customarily used for
+    software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code. (This alternative is allowed
+    only for noncommercial distribution and only if you received the
+    program in object code or executable form with such an offer, in
+    accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the executable. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major components
+(compiler, kernel, and so on) of the operating system on which the
+executable runs, unless that component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access
+to copy from a designated place, then offering equivalent access to
+copy the source code from the same place counts as distribution of the
+source code, even though third parties are not compelled to copy the
+source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and will
+automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will
+not have their licenses terminated so long as such parties remain in
+full compliance.
+
+5. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute
+the Program or its derivative works. These actions are prohibited
+by law if you do not accept this License. Therefore, by modifying or
+distributing the Program (or any work based on the Program), you indicate
+your acceptance of this License to do so, and all its terms and conditions
+for copying, distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further restrictions
+on the recipients' exercise of the rights granted herein. You are not
+responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute
+so as to satisfy simultaneously your obligations under this License
+and any other pertinent obligations, then as a consequence you may not
+distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who
+receive copies directly or indirectly through you, then the only way you
+could satisfy both it and this License would be to refrain entirely from
+distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any such
+claims; this section has the sole purpose of protecting the integrity of
+the free software distribution system, which is implemented by public
+license practices. Many people have made generous contributions to the
+wide range of software distributed through that system in reliance on
+consistent application of that system; it is up to the author/donor to
+decide if he or she is willing to distribute software through any other
+system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an
+explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail
+to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a version
+number of this License, you may choose any version ever published by
+the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software
+and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
+AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING
+BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
+LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
+TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
+HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+one line to give the program's name and an idea of what it does.
+Copyright (C) 19yy  name of author
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision
+comes with ABSOLUTELY NO WARRANTY; for details type `show w'.  This is
+free software, and you are welcome to redistribute it under certain
+conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License.  Of course, the commands
+you use may be called something other than `show w' and `show c'; they
+could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the program,
+if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications
+with the library. If this is what you want to do, use the GNU Library
+General Public License instead of this License.
+
+Return to GNU's home page.
+
+FSF & GNU inquiries & questions to gnu@gnu.org. Other ways to contact
+the FSF.
+
+Comments on these web pages to webmasters@www.gnu.org, send other
+questions to gnu@gnu.org.
+
+Copyright notice above.  Free Software Foundation, Inc., 59 Temple Place -
+Suite 330, Boston, MA 02111, USA
+
+Updated: 16 Feb 1998 tower
+
diff --git a/webstump/Makefile b/webstump/Makefile
new file mode 100644 (file)
index 0000000..170612e
--- /dev/null
@@ -0,0 +1,23 @@
+
+# Edit these variables
+
+WEBSTUMP_HOME = /home/webstump/live/webstump
+CC = cc
+
+# do not edit below
+all: verify c_compile
+
+verify:
+       if [ ! -x $(WEBSTUMP_HOME)/scripts/webstump.pl ] ;  then        \
+               echo $(WEBSTUMP_HOME)/scripts/webstump.pl does not;     \
+               echo point to a valid perl script.;                     \
+               echo Check the value of WEBSTUMP_HOME in Makefile;      \
+               exit 1;                                                 \
+       fi      \
+       
+       
+c_compile:
+       cd src; make WEBSTUMP_HOME=$(WEBSTUMP_HOME)
+
+clean: 
+       rm bin/wrapper
diff --git a/webstump/README b/webstump/README
new file mode 100644 (file)
index 0000000..5a9b721
--- /dev/null
@@ -0,0 +1,40 @@
+This is WebSTUMP v2.0. 
+
+INSTALLATION:
+
+0) [OPTIONAL] If you plan on moderating a picture newsgroup (a newsgroup
+with uuencoded or MIME-encoded binaries), you have to install several
+perl modules:
+
+MIME::Tools
+Convert::UU
+
+If you plan on moderating a text newsgroup, skip this step.
+
+1) Edit Makefile
+2) type make
+3) Edit config/webstump.cfg
+4) Create a symbolic link from your cgi-bin directory to the bin/wrapper
+   program
+5) Use create-newsgroup.pl perl script to create newsgroups.
+6) Edit your .procmailrc and add a recipe like this:
+
+######################################################################
+#
+# WebStump-related recipes
+#
+
+:0
+* ^X-Moderate-For:
+| $HOME/public_html/stump/webstump/scripts/file-message.pl
+
+7) Edit your admin/etc/modenv file and create a pseudo moderator
+like this:
+
+webstump@your.site     1       NO_BOARD_LIST,NO_ADVICE_LIST
+
+and make sure that all other mods are on vacation (set flag to 0).
+
+webstump@your.site should be substituted with an address where
+webstump resides 9and whose .procmailrc you just edited).
+
diff --git a/webstump/TODO b/webstump/TODO
new file mode 100644 (file)
index 0000000..c929728
--- /dev/null
@@ -0,0 +1 @@
+3) make the text of acks editable as well
diff --git a/webstump/config/convert-newsgroups.sh b/webstump/config/convert-newsgroups.sh
new file mode 100755 (executable)
index 0000000..6686297
--- /dev/null
@@ -0,0 +1,2 @@
+cat newsgroups.lst | grep -v  ^#|grep \\. \
+       | awk '{print "echo " $2 " > newsgroups/" $1 "/address.txt";}'
diff --git a/webstump/config/motd b/webstump/config/motd
new file mode 100644 (file)
index 0000000..8be4b7e
--- /dev/null
@@ -0,0 +1,2 @@
+Welcome to the new version of WebSTUMP.
+This version supports picture attachments and makes other improvements.
diff --git a/webstump/demo/error.html b/webstump/demo/error.html
new file mode 100644 (file)
index 0000000..ce32cb2
--- /dev/null
@@ -0,0 +1,7 @@
+<TITLE>These Pages Are Only a Demo</TITLE>
+
+<H1>These Pages Are Only a Demo</H1>
+
+You clicked on a button or a link that is functional in the
+real installation of WebSTUMP but is disabled on these
+demostration pages. Please try some other features on this page.
diff --git a/webstump/demo/login.html b/webstump/demo/login.html
new file mode 100644 (file)
index 0000000..b4d6be9
--- /dev/null
@@ -0,0 +1,38 @@
+\r
+<TITLE>misc.invest.financial-plan login</TITLE>\r
+<H1>misc.invest.financial-plan login</H1>\r
+\r
+\r
+  Please log into misc.invest.financial-plan. You can only log in if\r
+  you know your login id and know the secret password. You should not\r
+  give your password to any unauthorized user. Your login id and\r
+  password are NOT case sentitive, which means that, for example,\r
+  "xyzzy" and "XyZZY" are equally valid.<P>\r
+\r
+  Log in as "admin" if you want to add/delete users or change their\r
+  passwords. <P>\r
+\r
+  First Time Users: You have to log in as admin and add a moderator user\r
+  who will be able to moderate the newsgroup. Then log in again as that\r
+  user. If you are a new user, you have to have your admin password assigned to\r
+  you by the administrator. If you are a former user of a previous version of\r
+  WebSTUMP, type "admin" in the username prompt and your old "secret word"\r
+  as the admin password.<P>\r
+\r
+  <BLINK>\r
+       (DEMO VERSION: Just enter something and click the SUBMIT button)<P>\r
+  </BLINK>\r
+\r
+<FORM METHOD=get action=main-screen.html>\r
+ <INPUT NAME=action VALUE=moderation_screen TYPE=hidden>\r
+ Login: <INPUT NAME=moderator VALUE="" SIZE=20>\r
+ <BR>\r
+ Password: <INPUT NAME=password TYPE=password VALUE="" SIZE=20>\r
+ <BR>\r
+ <INPUT TYPE=submit VALUE="Proceed with Login">\r
+ <INPUT TYPE=reset VALUE="Reset">\r
+ <INPUT NAME=newsgroup VALUE="misc.invest.financial-plan" TYPE=hidden>\r
+ </FORM>\r
+\r
+<HR>Thank you for using <A HREF=http://www.algebra.com/~ichudov/stump>STUMP Robomoderator</A>.\r
+<P>\r
diff --git a/webstump/demo/main-screen.html b/webstump/demo/main-screen.html
new file mode 100644 (file)
index 0000000..7c8c1fe
--- /dev/null
@@ -0,0 +1,46 @@
+
+<TITLE>Main Moderation Screen: misc.invest.financial-plan</TITLE>
+<H1>Main Moderation Screen: misc.invest.financial-plan</H1>
+
+Welcome to the main moderation screen. Its main purpose is to 
+help you process most messages extremely quickly. For every message, it 
+presents you who sent it, as well as the first three non-blank lines.
+For those messages where the decision is obvious, simply select your
+decision (approve/reject etc) and click submit. For those messages which
+you would like to review in more details, do not select anything and
+use Review/Comment function from this screen or from a subsequent screen.
+Remember that if you do not make any decision, the article would stay in the
+queue.
+
+  <FORM METHOD=get action=error.html>
+  <INPUT NAME=action VALUE=approval_decision TYPE=hidden>
+ <INPUT NAME=newsgroup VALUE="misc.invest.financial-plan" TYPE=hidden>
+ <INPUT NAME=moderator VALUE="mifp" TYPE=hidden>
+ <INPUT NAME=password VALUE="disabled" TYPE=hidden>
+<HR>
+<B>From: "FSU/Professional Education" : FSU Online Certificate in Financial Planning(<A HREF=review.html>Review/Comment</A>)</B><BR>
+<INPUT TYPE=radio NAME="decision:896979716.19113" VALUE=approve>Approve
+<INPUT TYPE=radio NAME="decision:896979716.19113" VALUE=preapprove>PreApprove
+<INPUT TYPE=radio NAME="decision:896979716.19113" VALUE="reject offtopic">Reject Offtopic
+<INPUT TYPE=radio NAME="decision:896979716.19113" VALUE="reject harassing">Reject Harassing
+<INPUT TYPE=radio NAME="decision:896979716.19113" VALUE="reject formatting">Reject Formatting
+<PRE>
+>>>>>>  If you need help preparing to sit for the CFP Board Exam vis
+>>>>>>  http://cpd-online.fsu.edu/finplan 
+</PRE><HR><B> From: Leigh in raLeigh &lt;lmenconi@intrex.net&gt;: Re: Capital Gain on sale of new home(<A HREF=review.html>Review/Comment</A>)</B><BR>
+<INPUT TYPE=radio NAME="decision:896988527.3717" VALUE=approve>Approve
+<INPUT TYPE=radio NAME="decision:896988527.3717" VALUE=preapprove>PreApprove
+<INPUT TYPE=radio NAME="decision:896988527.3717" VALUE="reject offtopic">Reject Offtopic
+<INPUT TYPE=radio NAME="decision:896988527.3717" VALUE="reject harassing">Reject Harassing
+<INPUT TYPE=radio NAME="decision:896988527.3717" VALUE="reject formatting">Reject Formatting
+<PRE>
+>>>>>>  coastal1 wrote:
+>>>>>>  &gt; 
+>>>>>>  &gt; The sale of a home for $500,000 or less is now tax free
+</PRE><HR> <INPUT TYPE=submit VALUE=Submit>
+<INPUT TYPE=reset VALUE=Reset>
+<A HREF=password.html>Change Password</A></FORM>
+
+
+<HR>Thank you for using <A HREF=http://www.algebra.com/~ichudov/stump>STUMP Robomoderator</A>.
+<P>
diff --git a/webstump/demo/password.html b/webstump/demo/password.html
new file mode 100644 (file)
index 0000000..065d9a4
--- /dev/null
@@ -0,0 +1,21 @@
+
+<TITLE>Change Password</TITLE>
+<H1>Change Password</H1>
+
+All usernames and passwords are not case sensitive.
+<HR>Use this form to change your password:<BR>
+ <FORM METHOD=get action=error.html>
+ <INPUT NAME=action VALUE=validate_change_password TYPE=hidden>
+ <INPUT NAME=newsgroup VALUE="soc.culture.russian.moderated" TYPE=hidden>
+ <INPUT NAME=moderator VALUE="ichudov" TYPE=hidden>
+ <INPUT NAME=password VALUE="disabled" TYPE=hidden>
+
+ <BR>
+ New Password: <INPUT NAME=new_password VALUE="" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=Submit>
+ <INPUT TYPE=reset VALUE=Reset>
+ </FORM>
+
+<HR>Thank you for using <A HREF=http://www.algebra.com/~ichudov/stump>STUMP Robomoderator</A>.
+<P>
diff --git a/webstump/demo/review.html b/webstump/demo/review.html
new file mode 100644 (file)
index 0000000..0eb548b
--- /dev/null
@@ -0,0 +1,48 @@
+\r
+<TITLE>Main Moderation Screen: misc.invest.financial-plan</TITLE>\r
+<H1>Main Moderation Screen: misc.invest.financial-plan</H1>\r
+\r
+<A HREF=password.html>Change Password</A>\r
+<FORM METHOD=get action=error.html>\r
+<INPUT NAME=action VALUE=approval_decision TYPE=hidden>\r
+ <INPUT NAME=newsgroup VALUE="misc.invest.financial-plan" TYPE=hidden>\r
+ <INPUT NAME=moderator VALUE="mifp" TYPE=hidden>\r
+ <INPUT NAME=password VALUE="wrong" TYPE=hidden>\r
+<SELECT NAME="decision:896979716.19113">\r
+<OPTION VALUE="approve">Approve\r
+<OPTION VALUE="preapprove">Preapprove\r
+<OPTION VALUE="reject formatting">Reject -- message poorly formatted\r
+<OPTION VALUE="reject harassing">Reject -- message of harassing content\r
+<OPTION VALUE="reject offtopic">Reject -- a blatantly offtopic article, spam\r
+<BR></SELECT><BR>\r
+Comment: <INPUT NAME=comment VALUE="" SIZE=80><BR>\r
+<INPUT TYPE=submit VALUE="Submit">\r
+<INPUT TYPE=reset VALUE="Reset"><HR>\r
+</FORM>\r
+\r
+<PRE>\r
+Received: from relay4.UU.NET (relay4.UU.NET [192.48.96.14])\r
+       by www.video-collage.com (8.8.5/8.8.5) with ESMTP id NAA19091\r
+       for &lt;mifp@stump.algebra.com&gt;; Thu, 4 Jun 1998 13:01:49 -0400 (EDT)\r
+Received: from news.fsu.edu by relay4.UU.NET with ESMTP \r
+       (peer crosschecked as: news.fsu.edu [128.186.6.106])\r
+       id QQesim20595; Thu, 4 Jun 1998 13:02:58 -0400 (EDT)\r
+Received: (from news@localhost) by news.fsu.edu (8.8.3/8.7.3) id NAA20293; Thu, 4 Jun 1998 13:00:22 -0400 (EDT)\r
+To: misc-invest-financial-plan@uunet.uu.net\r
+From: "FSU/Professional Education" &lt;mcathcart@cpd.fsu.edu&gt;\r
+Newsgroups: misc.invest.financial-plan\r
+Subject: FSU Online Certificate in Financial Planning ::KEGHF894ND50MJ/89697971619105\r
+Organization: Fsu\r
+Message-ID: &lt;01bd8fda$7b61ee60$7a4aba80@micr531886-0004.fsu.edu&gt;\r
+X-Newsreader: Microsoft Internet News 4.70.1161\r
+\r
+If you need help preparing to sit for the CFP Board Exam visit this site:\r
+\r
+http://cpd-online.fsu.edu/finplan \r
+\r
+\r
+</PRE>\r
+\r
+\r
+<HR>Thank you for using <A HREF=http://www.algebra.com/~ichudov/stump>STUMP Robomoderator</A>.\r
+<P>\r
diff --git a/webstump/demo/welcome.html b/webstump/demo/welcome.html
new file mode 100644 (file)
index 0000000..a586b68
--- /dev/null
@@ -0,0 +1,18 @@
+\r
+<TITLE>Welcome to WebSTUMP</TITLE>\r
+<H1>Welcome to WebSTUMP</H1>\r
+\r
+Welcome to WebSTUMP, the moderators' front end for <A\r
+HREF=http://www.algebra.com/~ichudov/stump>STUMP</A> users -- USENET newsgroup\r
+moderators. Only authorized users are allowed to log into this\r
+program.\r
+\r
+<HR>\r
+Newsgroups Status:<BR>\r
+<TABLE BORDER=3><TR><TD><A HREF=login.html>alt.religion.wicca.moderated</A></TD><TD>2 messages in queue<BR></TD>\r
+<TR><TD><A HREF=login.html>soc.culture.russian.moderated</A></TD><TD>0 messages in queue<BR></TD>\r
+<TR><TD><A HREF=login.html>misc.invest.financial-plan</A></TD><TD>1 messages in queue<BR></TD>\r
+<TR><TD><A HREF=login.html>alt.sexual.abuse.recovery.moderated</A></TD><TD>19 messages in queue<BR></TD>\r
+</TABLE>\r
+<HR>Thank you for using <A HREF=http://www.algebra.com/~ichudov/stump>STUMP Robomoderator</A>.\r
+<P>\r
diff --git a/webstump/doc/help/bad.posters.list.html b/webstump/doc/help/bad.posters.list.html
new file mode 100644 (file)
index 0000000..fd086aa
--- /dev/null
@@ -0,0 +1,13 @@
+Bad posters list is the list of email addresses (no names) of posters who
+you would like to permanently bar from your newsgroup. It can be a list like
+this<P>
+
+<PRE>
+spammer@savetrees.com
+troll@aol.com
+</PRE>
+
+Of course, populating this list is a matter of your policy. however, I
+want to warn new moderators that blacklisting is a controversial practice,
+and if it is ever to be used, it should be used for a really good reason.
+
diff --git a/webstump/doc/help/bad.subjects.list.html b/webstump/doc/help/bad.subjects.list.html
new file mode 100644 (file)
index 0000000..d397be2
--- /dev/null
@@ -0,0 +1,17 @@
+Banned subects list is the list of fragments of Subject: fields or whole
+Subject: fields you would like to permanently bar from your newsgroup. It
+can be a list like this<P>
+
+<PRE>
+Gun control
+Abortion
+</PRE>
+
+That means that no message containing (in Subject:) words "gun control"
+or "abortion" would ever be posted.<P>
+
+Of course, populating this list is a matter of your policy. However, I
+want to warn new moderators that banning subjects would also have
+the effect of preventing meta-discussions. For instance, someone
+suggesting that Abortion should now be discussed would not be able to
+post this suggestion.
diff --git a/webstump/doc/help/bad.words.list.html b/webstump/doc/help/bad.words.list.html
new file mode 100644 (file)
index 0000000..43f5261
--- /dev/null
@@ -0,0 +1,21 @@
+Banned words list is the list of fragments of articles which you would
+like to permanently bar from your newsgroup. For example, if you
+never want articles mentioning 1-900 anywhere, you would put 1-900
+into that list.<P>
+
+It can be a list like this<P>
+
+<PRE>
+1-900
+make money fast
+XXX
+</PRE>
+
+That means that no message containing words 1-900, "make money fast",
+and "xxx" will ever be posted.<P>
+
+Of course, populating this list is a matter of your policy. However, I
+want to warn new moderators that banning words would also have
+the effect of preventing meta-discussions. For instance, someone
+suggesting that 1-900 should now be discussed would not be able to
+post this suggestion.
diff --git a/webstump/doc/help/filter-lists.html b/webstump/doc/help/filter-lists.html
new file mode 100644 (file)
index 0000000..0d41607
--- /dev/null
@@ -0,0 +1,48 @@
+WebSTUMP has a capability to process certain articles automatically,
+without involving a human moderator. For instance, if you decide that a
+certain thread has strayed off topic, and you no longer want ANY articles
+from that thread to ever appear, you can add the Subject of that thread
+to the list of bad threads. That will cause all submissions with that
+subject header to be automatically rejected.<P>
+
+Generally, WebSTUMP users can do filtering by the following criteria:
+
+<UL>
+  <LI> By poster
+  <LI> By Subject
+  <LI> use the list of suspicious words
+</UL>
+
+The algorithm for every incoming article is as follows:
+
+<OL>
+
+  <LI> Reject message if the author is listed in <A
+       HREF=##bad.posters.list>the List of Banned Posters.</A>.
+
+  <LI> Reject message if the Subject: field is listed in <A
+       HREF=##bad.subjects.list>the List of Banned Subjects</A>.
+
+  <LI> Reject message if the article contains words from <A
+       HREF=##bad.words.list>the List of Banned Words</A>.
+
+  <LI> File message for human review if the author is listed in <A
+       HREF=##watch.posters.list>the List of Untrusted Posters</A>.
+
+  <LI> File message for human review if the Subject is listed in <A
+       HREF=##watch.subjects.list>the List of Suspicious Subjects</A>.
+
+  <LI> File message for human review if the article contains words from <A
+       HREF=##watch.words.list>the List of Suspicious Words</A>.
+
+  <LI> Approve message if the author is listed in <A
+       HREF=##good.posters.list>the List of Preapproved Posters</A>.
+
+  <LI> Approve message if the Subject: field is listed in <A
+       HREF=##good.subjects.list>the List of Preapproved Subjects</A>.
+
+  <LI> File all still unprocessed articles for human review.
+</OL>
+
+To edit filter lists, log in as "admin" to your newsgroup. Choose the
+proper list and hit "Edit". be careful not to lose any changes.
diff --git a/webstump/doc/help/good.posters.list.html b/webstump/doc/help/good.posters.list.html
new file mode 100644 (file)
index 0000000..cc5f28e
--- /dev/null
@@ -0,0 +1,20 @@
+Good posters list is the list of email addresses (no names) of trusted
+posters.  All messages coming from "trusted posters", provided that they
+do not fall within the purview of (<A HREF=##filter-lists>other
+filters</A>), are automatically approved.<P>
+
+Exzample:<P>
+
+<PRE>
+goodguy@aol.com
+nicegal@my-dejanews.com
+racingfan@webtv.net
+</PRE>
+
+Using a preapproved list extensively does two good things:
+
+<UL>
+  <LI> Reduces your workload by up to 90%
+  <LI> Dramatically improves timeliness of discussions and creates a more
+       discussion-like atmosphere.
+</UL
diff --git a/webstump/doc/help/good.subjects.list.html b/webstump/doc/help/good.subjects.list.html
new file mode 100644 (file)
index 0000000..cc823d5
--- /dev/null
@@ -0,0 +1,13 @@
+Good subjects list is the list of subjects that need no moderator review.
+All messages with those subjects (or, to be more exact, with subjects
+that contain one of the lines listed in the configuration file), provided
+that they do not fall within the purview of (<A HREF=##filter-lists>other
+filters</A>), are automatically approved.<P>
+
+Example:<P>
+
+<PRE>
+An interestnig question
+Any fans in here
+</PRE>
+
diff --git a/webstump/doc/help/watch.posters.list.html b/webstump/doc/help/watch.posters.list.html
new file mode 100644 (file)
index 0000000..25f355b
--- /dev/null
@@ -0,0 +1,17 @@
+Untrusted posters list is the list of email addresses (no names) of
+posters whose posts should always be reviewed by mods, even if they are
+posting to a preapproved thread.<P>
+
+Example:<P>
+
+<PRE>
+anonymous
+nobody
+remailer
+mixmaster
+losingtemper@aol.com
+</PRE>
+
+The above would ensure that all addresses containing substrings above
+would always come to human attention. A nice way to deal with anon
+remailers.
diff --git a/webstump/doc/help/watch.subjects.list.html b/webstump/doc/help/watch.subjects.list.html
new file mode 100644 (file)
index 0000000..9747b36
--- /dev/null
@@ -0,0 +1,15 @@
+Untrusted subjects list is the list of subjects
+which should always be reviewed by mods, even if the messages are posted by 
+preapproved posters.<P>
+
+Example:<P>
+
+<PRE>
+abortion
+gun control
+forger
+</PRE>
+
+The above would ensure that all discussions with Subject: containing one
+of the above words would be monitored. A nice way to deal with
+controversial topics or flames.
diff --git a/webstump/doc/help/watch.words.list.html b/webstump/doc/help/watch.words.list.html
new file mode 100644 (file)
index 0000000..2b9001a
--- /dev/null
@@ -0,0 +1,24 @@
+List of words to watch is the list of text patterns 
+which, if they appear in a message, should ensure that the message is
+not automatically approved and is stored in WebSTUMP for human review.<P>
+
+These words may be an indication of a potential spam, or a flame
+etc. Perhaps, for many groups, putting common profanities, as well
+as things like XXX and 1-900, is a good strategy. Note however, that
+there is generally no reason to forbid the use of profanity, as such.<P>
+
+Example:<P>
+
+<PRE>
+happy99.exe
+fuck
+shit
+cunt
+XXX
+1-900
+1-800
+make money fast
+</PRE>
+
+My advice is to watch your group for patterns appearing in offtopic posts,
+and use them to your advantage to help you with filtering.
diff --git a/webstump/images/bg1.jpg b/webstump/images/bg1.jpg
new file mode 100644 (file)
index 0000000..b8edc8a
Binary files /dev/null and b/webstump/images/bg1.jpg differ
diff --git a/webstump/images/construction.gif b/webstump/images/construction.gif
new file mode 100644 (file)
index 0000000..cb166f8
Binary files /dev/null and b/webstump/images/construction.gif differ
diff --git a/webstump/images/help.gif b/webstump/images/help.gif
new file mode 100644 (file)
index 0000000..0fe148a
Binary files /dev/null and b/webstump/images/help.gif differ
diff --git a/webstump/images/new_tiny2.gif b/webstump/images/new_tiny2.gif
new file mode 100644 (file)
index 0000000..eacd4d8
Binary files /dev/null and b/webstump/images/new_tiny2.gif differ
diff --git a/webstump/images/no_image.gif b/webstump/images/no_image.gif
new file mode 100644 (file)
index 0000000..dbdee1e
Binary files /dev/null and b/webstump/images/no_image.gif differ
diff --git a/webstump/images/smiley.gif b/webstump/images/smiley.gif
new file mode 100644 (file)
index 0000000..77eee91
Binary files /dev/null and b/webstump/images/smiley.gif differ
diff --git a/webstump/images/star.gif b/webstump/images/star.gif
new file mode 100644 (file)
index 0000000..853f810
Binary files /dev/null and b/webstump/images/star.gif differ
diff --git a/webstump/images/warning_big.gif b/webstump/images/warning_big.gif
new file mode 100644 (file)
index 0000000..881d1fe
Binary files /dev/null and b/webstump/images/warning_big.gif differ
diff --git a/webstump/index.html b/webstump/index.html
new file mode 100644 (file)
index 0000000..6600b13
--- /dev/null
@@ -0,0 +1,22 @@
+<TITLE>WebSTUMP</TITLE>
+
+<H1>WebSTUMP</H1>
+
+WebSTUMP is a user interface for STUMP and Gatekeeper users. It gives
+moderators an easy and universally accessible Web interface through which
+they can moderate newsgroups. Use of WebSTUMP requires NO computer
+skills beyond  knowing how to operate a Web browser.<P>
+
+The demo pages give you an opportunity to "test drive" STUMP. They
+are not functional in the sense that your actions do not affect anyone.
+However, they do give you a "look and feel" of how WebSTUMP interface
+looks to moderators.<P>
+
+On the next page, click on a newsgroup name to begin. Try clicking on
+various links and buttons and see what happens.<P>
+
+<CENTER><H2>
+<A HREF=demo/welcome.html>Begin Test Drive!</A>
+</H2></CENTER>
+<HR>
+<A HREF=..>Back to STUMP Homepage</A>
diff --git a/webstump/scripts/create-newsgroup.pl b/webstump/scripts/create-newsgroup.pl
new file mode 100755 (executable)
index 0000000..e4e67a7
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+#
+# This script creates a new newsgroup.
+#
+
+if( !($0 =~ /\/scripts\/create-newsgroup\.pl$/) ) {
+  die "This script can only be called with full path name!!!";
+}
+
+$webstump_home = $0;
+$webstump_home =~ s/\/scripts\/create-newsgroup\.pl$//;
+
+require "$webstump_home/config/webstump.cfg";
+require "$webstump_home/scripts/webstump.lib.pl";
+
+&init_webstump;
+
+$newsgroup = @ARGV[0];
+$address = @ARGV[1];
+$password = @ARGV[2];
+
+print "Creating newsgroup:
+Name: $newsgroup
+Approval Address: $address
+Admin password: $password
+Press ENTER to continue, ^C to interrupt:\n";
+
+<STDIN>;
+
+print "Adding $newsgroup to $webstump_home/config/newsgroups.lst...";
+&append_to_file( "$webstump_home/config/newsgroups.lst", 
+                "$newsgroup  $address\n" );
+mkdir "$webstump_home/queues/$newsgroup", 0755;
+print "\b\b\b done.\n";
+
+$dir = "$webstump_home/config/newsgroups/$newsgroup";
+
+print "Creating $dir...";
+mkdir $dir, 0755;
+print "\b\b\b done.\n";
+
+print "Creating files in $dir...";
+
+&append_to_file( "$dir/blacklist", "" );
+&append_to_file( "$dir/moderators", "ADMIN \U$password\n" );
+&append_to_file( "$dir/rejection-reasons", 
+"offtopic::a blatantly offtopic article, spam
+harassing::message of harassing content
+charter::message poorly formatted
+" );
+&append_to_file( "$dir/whitelist", "" );
+print "\b\b\b done.\n";
diff --git a/webstump/scripts/file-message.pl b/webstump/scripts/file-message.pl
new file mode 100755 (executable)
index 0000000..59d5f17
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+#
+# This script reads a message from stdin, figures out which newsgroup's
+# queue it should be saved to, and saves it.
+#
+#
+# Figure out the home directory
+#
+
+umask 022;
+
+if( !($0 =~ /\/scripts\/file-message\.pl$/) ) {
+  die "This script can only be called with full path name!!!";
+}
+
+$webstump_home = $0;
+$webstump_home =~ s/\/scripts\/file-message\.pl$//;
+
+open STDOUT, ">> $webstump_home/../errs" or die $!;
+open STDERR, ">&STDOUT" or die $!;
+
+require "$webstump_home/config/webstump.cfg";
+require "$webstump_home/scripts/webstump.lib.pl";
+require "$webstump_home/scripts/filter.lib.pl";
+require "$webstump_home/scripts/mime-parsing.lib";
+
+&init_webstump;
+
+$time = time;
+$directory = "$webstump_home/tmp/dir_$time" . "_$$";
+
+while( <STDIN> )
+{
+  chop;
+  if( /^X-Moderate-For: / ) {
+    s/^X-Moderate-For: //;
+    $newsgroup = $_;
+  } elsif ( /^Subject: / ) {
+    $Subject = $_;
+  } elsif ( /^$/ ) {
+    last;
+  }
+}
+
+die
+"This message did not look like it came from STUMP because it did not
+contain the X-Moderate-For: header"
+       if( !$newsgroup );
+
+while( ($_ = <STDIN>) && !($_ =~ /^\@+$/ )) {};
+
+#
+# this will also take away the "From " line.
+#
+while( ($_ = <STDIN>) && ($_ =~ /^$/ )) {};
+
+my ($entity, $prolog);
+
+if( $use_mime eq "yes" ) {
+  ($entity, $prolog) = &decode_mime_message( $directory );
+} else { # no MIME
+  $prolog = &decode_plaintext_message( $directory );
+}
+
+$prolog = $Subject . "\n" . $prolog;
+
+die "This message did not look like it came from STUMP because it did not
+    contain the X-Moderate-For: header"
+       if( !$newsgroup );
+
+$queue_dir = &getQueueDir( $newsgroup ) 
+       || die "Newsgroup $newsgroup is not listed in the newsgroups database";
+
+mkdir $queue_dir, 0755; # it is OK if this fails
+chmod 0755, $queue_dir;
+
+die "$queue_dir does not exist or is not writable"
+       if( ! -d $queue_dir || ! -w $queue_dir );
+
+open( PROLOG, ">$directory/stump-prolog.txt" );
+print PROLOG $prolog;
+close( PROLOG );
+
+#open( FULL, ">$directory/full_message.txt" );
+#print FULL $entity->as_string;
+#close( FULL );
+
+my $dir = "dir_$time" . "_$$";
+rename $directory, "$queue_dir/$dir";
+
+&init_webstump;
+$request{"newsgroup"} = $newsgroup;
+
+#sub review_incoming_message { # Newsgroup, From, Subject, Message, Dir
+
+&review_incoming_message( $newsgroup, $Article_From, $Subject, 
+                          $Article_Subject, $Article_Head . $Article_Body, $dir );
diff --git a/webstump/scripts/filter.lib.pl b/webstump/scripts/filter.lib.pl
new file mode 100644 (file)
index 0000000..b98b22d
--- /dev/null
@@ -0,0 +1,143 @@
+#
+#
+# This library of functions is used for filtering messages.
+#
+
+
+# processes approval decision.
+#
+# Arguments: 
+#
+# Subject, newsgroup, ShortDirectoryName, decision, comment
+
+sub process_approval_decision {
+
+  my $comment = pop( @_ );
+  my $decision = pop( @_ );
+  my $ShortDirectoryName = pop( @_ );
+  my $newsgroup = pop( @_ );
+  my $Subject = pop( @_ );
+
+  my $address = $newsgroups_index{$newsgroup};
+
+  my $message = "To: $newsgroups_index{$newsgroup}\n" .
+               "Subject: $Subject\n" .
+                "Organization: http://www.algebra.com/~ichudov/stump\n";
+
+  $message .= "\n$decision\n";
+  $message .= "comment $comment\n" if $comment;
+  &email_message( $message, $address );
+
+print STDERR "DECISION: $decision for $ShortDirectoryName sent to $address, for $newsgroup\n";
+
+  &rmdir_rf( &article_file_name( $ShortDirectoryName ) );
+
+}
+
+
+###################################################################### checkAck
+# checks the string matches one of the substrings. A name is matched
+# against the substrings as regexps and substrings as literal substrings.
+#
+# Arguments: address, listname
+sub name_is_in_list { # address, listname
+  my $listName = pop( @_ );
+  my $address = pop( @_ );
+
+  my $item = "";
+  my $Result = "";
+
+  $address = "\L$address";
+
+  open( LIST, &full_config_file_name( $listName ) ) || return "";
+
+  while( $item = <LIST> ) {
+
+    chop $item;
+
+    next if $item =~ /^\s*$/;
+
+    my $quoted_item = quotemeta( $item );
+
+    if( eval { $address =~ /$item/i; } || $address =~ /$quoted_item/i ) {
+      $Result = $item;
+    }
+  }
+
+  close( LIST );
+
+  return $Result;
+}
+
+
+######################################################################
+# reviews incoming message and decides: approve, reject, keep
+# in queue for human review
+#
+# Arguments: Newsgroup, From, Subject, Message, Dir
+#
+# RealSubject is the shorter subject from original posting
+sub review_incoming_message { # Newsgroup, From, Subject, RealSubject, Message, Dir
+  my $dir = pop( @_ );
+  my $message = pop( @_ );
+  my $real_subject = pop( @_ );
+  my $subject = pop( @_ );
+  my $from = pop( @_ );
+  my $newsgroup = pop( @_ );
+
+  if( &name_is_in_list( $from, "bad.posters.list" ) ) {
+    &process_approval_decision( $subject, $newsgroup, $dir, "reject abuse", "" );
+    return;
+  }
+
+  if( &name_is_in_list( $real_subject, "bad.subjects.list" ) ) {
+    &process_approval_decision( $subject, $newsgroup, $dir, "reject thread", "" );
+    return;
+  }
+
+  if( &name_is_in_list( $message, "bad.words.list" ) ) {
+    &process_approval_decision( $subject, $newsgroup, $dir, "reject charter", 
+    "Your message has been autorejected because it appears to be off topic
+    based on our filtering criteria. Like everything, filters do not
+    always work perfectly and you can always appeal this decision." );
+    return;
+  }
+
+  my $warning_file = &article_file_name( $dir ) . "/stump-warning.txt";
+  my $match;
+
+  $ignore_demo_mode = 1;
+
+  if( $match = &name_is_in_list( $from, "watch.posters.list" ) ) {
+    &append_to_file( $warning_file, "Warning: poster '$from' matches '$match' from the list of suspicious posters\n" );
+print STDERR "Filing Article for review because poster '$from' matches '$match'\n";
+    return; # file message
+  }
+
+  if( $match = &name_is_in_list( $real_subject, "watch.subjects.list" ) ) {
+    &append_to_file( $warning_file, "Warning: subject '$real_subject' matches '$match' from the list of suspicious subjects\n" );
+print STDERR "Filing Article for review because subject '$subject' matches '$match'\n";
+    return; # file message
+  }
+
+  if( $match = &name_is_in_list( $message, "watch.words.list" ) ) {
+    &append_to_file( $warning_file, "Warning: article matches '$match' from the list of suspicious words\n" );
+print STDERR "Filing Article for review because article matches '$match'\n";
+    return; # file message
+  }
+
+  if( &name_is_in_list( $from, "good.posters.list" ) ) {
+    &process_approval_decision( $subject, $newsgroup, $dir, "approve", "" );
+    return;
+  }
+
+  if( &name_is_in_list( $real_subject, "good.subjects.list" ) ) {
+    &process_approval_decision( $subject, $newsgroup, $dir, "approve", "" );
+    return;
+  }
+
+  # if the message remains here, it is stored for human review.
+
+}
+
+1;
diff --git a/webstump/scripts/gatekeeper-file-message.pl b/webstump/scripts/gatekeeper-file-message.pl
new file mode 100755 (executable)
index 0000000..8b0db62
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+#
+# This script reads a message from stdin, figures out which newsgroup's
+# queue it should be saved to, and saves it.
+#
+#
+# Figure out the home directory
+#
+
+if( !($0 =~ /\/scripts\/gatekeeper-file-message\.pl$/) ) {
+  die "This script can only be called with full path name!!!";
+}
+
+$webstump_home = $0;
+$webstump_home =~ s/\/scripts\/gatekeeper-file-message\.pl$//;
+
+require "$webstump_home/config/webstump.cfg";
+require "$webstump_home/scripts/webstump.lib.pl";
+
+&init_webstump;
+
+$Subject = "";
+
+$newsgroup = @ARGV[0] || die "Syntax: $0 newsgroup.name";
+
+$queue_dir = &getQueueDir( $newsgroup ) 
+       || die "Newsgroup $newsgroup is not listed in the newsgroups database";
+
+mkdir $queue_dir, 0700; # it is OK if this fails
+
+die "$queue_dir does not exist or is not writable"
+       if( ! -d $queue_dir || ! -w $queue_dir );
+
+$time = time;
+$file = "$queue_dir/$time.$$";
+open( QUEUE_FILE, ">$file" ) || die "Could not open $file for writing";
+
+while( <STDIN> ) {
+  print QUEUE_FILE;
+}
+
+close( QUEUE_FILE );
diff --git a/webstump/scripts/gatekeeper.lib b/webstump/scripts/gatekeeper.lib
new file mode 100644 (file)
index 0000000..3adca4b
--- /dev/null
@@ -0,0 +1,115 @@
+# These functions are useful for Gatekeeper product.
+
+# check if the header line is to be ignored
+sub ignore_header {
+  my $header = pop( @_ );
+  my @delete_headers = ( "NNTP-Posting-Host", "X-Originating-IP",
+                         "Received", "Recieved", "Date", "X400",
+                         "Approved" );
+  foreach (@delete_headers) {
+    return "yes" if( $header =~ /^$_:/i );
+  }
+
+  return ""; # no
+}
+
+
+######################################################################
+# this function reads an article from a file and prepares it for posting.
+sub prepareArticle {
+
+  my $file = pop( @_ );
+
+  my @delete_headers = ( "NNTP-Posting-Host", "X-Originating-IP",
+                        "Received", "Recieved", "Date", "X400",
+                         "Approved" );
+
+  open ARTICLE, $file || return "";
+
+  my $headers = "", $body = "";
+
+  # headers
+  my $header = "";
+  my $newsgroups_present = 0;
+
+  # header
+  while( <ARTICLE> ) {
+
+    next if( /^From /);
+
+    if( /^$/ || /^\S/ ) { # non-whitespace or empty line
+      if( $header ) {
+        # process old header
+       if( &ignore_header( $header ) ) {
+          $header = "";
+          next;
+        }
+
+       if( $header =~ /^Newsgroups: / ) {
+          $newsgroups_present = 1;
+          if( ! ($header =~ $newsgroup)) {
+            chop $header;
+            $header .= ",$newsgroup\n";
+          }
+        }
+
+        $headers .= $header;
+        last if $_ eq "\n";
+      }
+
+      $header = $_;
+    } else { # whitespace
+      $header .= $_;
+    }
+  }
+
+  $headers .= "$newsgroup\n" if( !$newsgroups_present );
+
+  $body .= $_ while( <ARTICLE> );
+
+print "Header:\n\n$header\nBody:\n\n$body\n";
+
+  return $headers . $body;
+}
+
+# processApproved file comment
+sub processApproved {
+  my $comment = pop( @_ );
+  my $file = pop( @_ );
+
+  my $article = &prepareArticle( $file );
+
+  if( $comment ) {
+    $article .= "
+=================================================================
+== Moderator's comment: $comment\n";
+  }
+
+  &email_message( $article, $posting_gateway ) if( $posting_gateway );
+  
+  if( $posting_spool_dir ) {
+    my $time = time;
+    my $spoolfile = "$posting_spool_dir/$$.$time";
+    open( SPOOL, ">$posting_spool_dir/$$.$time" ) 
+      || &error( "Can't open $spoolfile" );
+    print SPOOL $message;
+    close( SPOOL );
+  }
+}
+
+
+# processDecision file decision+reason comment
+sub gk_approval_decision{
+  my $comment = pop( @_ );
+  my $decision = pop( @_ );
+  my $file = pop( @_ );
+
+  if( $decision eq "approve" ) {
+    &processApproved( $file, $comment );
+  } elsif( $decision eq "reject" ) {
+    my ($dummy, $reason) = split( $decision );
+    &processRejected( $file, $reason, $comment );
+  }
+}
+
+1;
diff --git a/webstump/scripts/html_output.pl b/webstump/scripts/html_output.pl
new file mode 100644 (file)
index 0000000..e99c8d1
--- /dev/null
@@ -0,0 +1,877 @@
+#
+# This is a module with functions for HTML output.
+#
+# I separate it from the main STUMP stuff because these functions are
+# bulky and not very interesting.
+#
+#
+
+use POSIX;
+
+sub begin_html {
+  my $title = pop( @_ );
+  print 
+"Content-Type: text/html\n\n
+<TITLE>$title</TITLE>
+<BODY>
+<H1>$title</H1>\n\n";
+
+  if( &is_demo_mode ) {
+    print "<B> You are operating in demonstration mode. User actions will have no effect.</B><HR>\n";
+  }
+  
+}
+
+sub end_html {
+  print "\n<HR>Thank you for using <A HREF=$STUMP_URL>STUMP Robomoderator</A>.
+<!-- <BR>
+Click <A HREF=$base_address>here</A> to return to WebSTUMP. -->
+";
+}
+
+# prints a link to help
+# accepts topic id and topic name.
+#
+sub link_to_help {
+  my $topic_name = pop( @_ );
+  my $topic = pop( @_ );
+
+  #&print_image( "help.gif", "" );
+
+  print "<A HREF=$base_address?action=help&topic=$topic TARGET=new>Click here for help on $topic_name</A>\n";
+}
+
+#
+# prints image and an alt text
+#
+sub print_image { # image_file, alt_text
+  my $alt = pop( @_ );
+  my $file = pop( @_ );
+
+  print "<IMG SRC=$base_address_for_files/images/$file ALT=\"$alt\" ALIGN=BOTTOMP>\n";
+}
+
+# prints the welcome page and login screen.
+sub html_welcome_page {
+  &begin_html( "Welcome to WebSTUMP" );
+
+  print 
+
+"Welcome to WebSTUMP, the moderators' front end for <A
+HREF=http://www.algebra.com/~ichudov/stump>STUMP</A> users -- USENET newsgroup
+moderators. Only authorized users are allowed to log into this
+program.
+
+<HR>";
+
+  my $motd_file = "$webstump_home/config/motd";
+
+  if( -f $motd_file && -r $motd_file ){
+    open( MOTD, $motd_file );
+    print "<B>Message of the Day:</B><BR><PRE>\n";
+    print while( <MOTD> );
+    close( MOTD );
+    print "</PRE><HR>\n";
+  }
+
+  print "
+Newsgroups Status:<BR>
+<TABLE BORDER=3>\n";
+
+  for( sort @newsgroups_array ) {
+    print "<TR><TD>";
+    
+    my $count = &get_article_count( $_ );
+
+    print " <A HREF=$base_address?action=login_screen\&newsgroup=$_>$_</A>";
+    &print_image( "smiley.gif", "" ) if $count;
+    print "</TD>";
+
+
+    print "<TD>$count messages in queue<BR></TD>";
+#    print "<TD><A HREF=$base_address?action=init_request_newsgroup_creation\&newsgroup=$_>Request creation</A></TD>\n";
+  }
+
+  print "</TABLE>\n";
+  print "<HR>Note: click on the newsgroup to login in as moderator. 
+<!-- Click on 'Request Creation' to ask a sysadmin at a specific domain
+to carry your newsgroup. -->\n<HR>
+<A HREF=$base_address?action=admin_login>Click here to administer this WebSTUMP installation</A>
+";
+  &end_html;
+}
+
+# prints the login screen for newsgroup.
+sub html_login_screen {
+  my $newsgroup = $request{'newsgroup'} || &error( "newsgroup not defined" );
+
+  my $count = &get_article_count( $newsgroup );
+
+
+  if( $count ) {
+    &begin_html( "$count articles in queue for $newsgroup" );
+  } else {
+    &begin_html( "Empty Queue for $newsgroup" );
+  }
+
+  print
+" Welcome to the Moderation  Center for  $newsgroup. Please bookmark
+this page. <HR>";
+
+
+  my $color = "", $end_color = "";
+
+  if( $count ) {
+    $color = "<font color=red>";
+    $end_color = "<font color=black>";
+  }
+
+  print 
+"<FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=moderation_screen TYPE=hidden>
+  $color ($count ";
+  
+  &print_image( "new_tiny2.gif", "new" ) if $count;
+
+  print " articles available)<BR> $end_color
+ Login: <INPUT NAME=moderator VALUE=\"\" SIZE=20>
+ <BR>
+ Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=\"Proceed with Login\">
+ <INPUT TYPE=reset VALUE=\"Reset\">
+ <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
+ </FORM><HR>
+  Please log into $newsgroup. You can only log in if you know your login id
+  and know the secret password. You should not give your password to any
+  unauthorized user. Your login id and password are NOT case sentitive, 
+  which means that,
+  for example, \"xyzzy\" and \"XyZZY\" are equally valid.<P>
+";
+
+  print "
+ Log in as \"admin\" if you want to 
+<UL>
+  <LI> edit filtering lists.";
+
+  &link_to_help( "filter-lists", "Filter Lists" );
+
+  print "
+  <LI> add/delete users or change their passwords.
+  <LI> First Time Users: You have to log in as admin and add a moderator user
+  who will be able to moderate the newsgroup. Then log in again as that
+  user. If you are a new user, you have to have your admin password assigned to
+  you by the administrator.
+</UL>
+
+";
+  &end_html;
+}
+
+# prints the login screen for newsgroup.
+sub admin_login_screen {
+  &begin_html( "Administrative login" );
+
+  print
+"
+Attention: this page is only for the maintainer of the whole WebSTUMP
+installation. Please return to the main page if you are not the maintainer
+of this installation. <HR>
+";
+
+  print 
+"<FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=webstump_admin_screen TYPE=hidden>
+ Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=\"Proceed with Login\">
+ <INPUT TYPE=reset VALUE=\"Reset\">
+ </FORM>
+";
+
+  &end_html;
+}
+
+# main moderation page -- old version
+sub html_moderate_article {
+  my $newsgroup = &required_parameter( 'newsgroup' );
+  my $moderator = $request{'moderator'};
+  my $password = $request{'password'};
+  my $file = shift @_ || &required_parameter('file');
+
+  &begin_html( "Main Moderation Screen: $newsgroup" );
+  print "<HR>\n";
+
+  &read_rejection_reasons;
+
+  my $dir = "$queues_dir/$newsgroup";
+
+  if( -d "$dir/$file" && open( TEXT_FILES, "$dir/$file/text.files.lst" ) ) {
+
+      print "<HR>\n" if &print_article_warning( $file );
+
+      print "<PRE>\n";
+      my $filename;
+      my $inhead= 1;
+      while( $filename = <TEXT_FILES> ) {
+        open( ARTICLE, "$dir/$file/$filename" );
+        while( <ARTICLE> ) {
+         $embolden= m/^(?:from|subject)\s*\:/i;
+         s/\&/&amp;/g;
+          s/</&lt;/g;
+          s/>/&gt;/g;
+         $_= "<strong>$_</strong>" if $embolden;
+          print;
+         $inhead= 0 unless m/\S/;
+        }
+        close( ARTICLE );
+       $inhead= 0;
+      }
+
+      print "\n</PRE>\n\n";
+
+      &print_images( $newsgroup, "$dir/$file", $file);
+
+  } else {
+    print "This message ($dir/$file) no longer exists -- maybe it was " .
+          "approved or rejected by another moderator.";
+  }
+
+      print "<HR>
+<FORM NAME=decision METHOD=$request_method action=$base_address>
+";
+
+  print "
+<INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
+  &html_print_credentials;
+  print "<SELECT NAME=\"decision_$file\">
+<OPTION VALUE=\"approve\">Approve</OPTION>
+<OPTION VALUE=\"leave\">Put to back of queue</OPTION>
+<OPTION VALUE=\"consider\">Back of queue, adding mark requesting further consideration</OPTION>
+";
+
+      foreach (sort(keys %rejection_reasons)) {
+        print "<OPTION VALUE=\"reject $_\">Reject -- $rejection_reasons{$_}</OPTION>\n";
+      }
+
+      print "<BR>";
+
+      print "</SELECT><BR> Comment: <INPUT NAME=comment VALUE=\"\" SIZE=80><BR>";
+
+  print "<BR>
+<INPUT TYPE=radio NAME=poster_decision VALUE=nothing CHECKED>Don't change poster's status</INPUT>
+<INPUT TYPE=radio NAME=poster_decision VALUE=preapprove 
+>Preapprove poster</INPUT>
+<INPUT TYPE=radio NAME=poster_decision VALUE=ban 
+  ONCLICK=\"alert( 'Banning a poster is a controversial practice'); \"
+> Ban All Posts by this Person (Careful!)</INPUT>
+<BR><BR>
+<INPUT TYPE=radio NAME=thread_decision VALUE=nothing CHECKED>Don't change thread's status</INPUT>
+<!-- <INPUT TYPE=radio NAME=thread_decision VALUE=preapprove>Preapprove thread, by Subject:</INPUT> -->
+<BR>
+
+<INPUT TYPE=radio NAME=thread_decision VALUE=ban
+  ONCLICK=\"alert( 'Banning a thread is a controversial practice'); \"
+>Ban Entire Thread By Subject (Careful!)</INPUT>
+<INPUT TYPE=radio NAME=thread_decision VALUE=watch>Put Entire thread on a Watch, by Subject:</INPUT>
+
+<BR><BR>
+<I>
+NOTE: Decisions to ban and preapprove posters and threads can be reversed by 
+logging in as \"admin\" and editing respective lists of preapproved
+and banned threads  and posters.
+";
+
+  &link_to_help( "filter-lists", "automatic filtering and filter lists, blacklisting and preapproved threads." );
+
+  print "Be really careful about blacklisting of everyone except spammers.</I><BR><BR>
+
+<INPUT TYPE=radio NAME=next_screen VALUE=single CHECKED> 
+       Review ONE article in next screen
+<INPUT TYPE=radio NAME=next_screen VALUE=multiple> 
+       Review multiple articles in next screen
+<HR>
+
+<INPUT TYPE=submit VALUE=\"Submit\">
+<INPUT TYPE=submit NAME=skip_submit VALUE=\"Skip\">
+<INPUT TYPE=reset VALUE=\"Reset\">
+";
+
+      print "</FORM>\n\n";
+  print "<BR><A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
+        "moderator=$moderator&password=$password>Change Password</A>";
+
+  closedir( QUEUE );
+  &end_html;
+}
+
+# WebSTUMP administrative screen
+sub webstump_admin_screen {
+
+  &verify_admin_password;
+
+  my $password = $request{'password'};
+
+  &begin_html( "WebSTUMP Administration" );
+  print "
+<FORM METHOD=$request_method action=$base_address>
+<INPUT NAME=action VALUE=admin_add_newsgroup TYPE=hidden>
+<INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
+
+
+  print "
+<HR>
+Create a new newsgroup on the server:<BR>
+
+Newsgroup:<BR> <INPUT NAME=newsgroup_name VALUE=\"\" SIZE=50><BR>
+Address to send approved/rejected messages <BR>
+       <INPUT NAME=newsgroup_approved_address VALUE=\"\" SIZE=30><BR>
+Admin Password For this group:<BR> <INPUT NAME=newsgroup_password VALUE=\"\" SIZE=10><BR>
+<INPUT TYPE=submit VALUE=\"Submit\">
+<INPUT TYPE=reset VALUE=\"Reset\"><HR>
+";
+
+      print "</FORM>\n\n<PRE>\n";
+
+  &end_html;
+}
+
+# WebSTUMP "add newsgroup" function
+sub admin_add_newsgroup {
+
+  &verify_admin_password;
+
+  my $newsgroup = &required_parameter( 'newsgroup_name' );
+
+  $newsgroup =~ s/\///g;
+  $newsgroup = &untaint( $newsgroup );
+
+  my $address = &required_parameter( 'newsgroup_approved_address' );
+  my $password = &required_parameter( 'newsgroup_password' );
+
+  &user_error( "Newsgroup $newsgroup already exists" )
+    if defined $newsgroups_index{$newsgroup};
+
+  &user_error( "Password may only contain letters and digits" )
+    if( ! ($password =~ /^[a-zA-Z0-9]+$/ ) );
+
+  &begin_html( "WebSTUMP Administration: Newsgroup created" );
+
+  print "<PRE>\n\n";
+
+  print "Adding $newsgroup to $webstump_home/config/newsgroups.lst...";
+  mkdir "$webstump_home/queues/$newsgroup", 0755;
+  print " done.\n";
+  
+  $dir = "$webstump_home/config/newsgroups/$newsgroup";
+  
+  print "Creating $dir...";
+  mkdir $dir, 0755;
+  print " done.\n";
+  
+  print "Creating files in $dir...";
+  
+  &append_to_file( "$dir/blacklist", "" );
+  &append_to_file( "$dir/address.txt", "$address\n" );
+  &append_to_file( "$dir/moderators", "ADMIN \U$password\n" );
+  &append_to_file( "$dir/rejection-reasons",
+"offtopic::a blatantly offtopic article, spam
+harassing::message of harassing content
+charter::message poorly formatted
+" );
+  &append_to_file( "$dir/whitelist", "" );
+  print " done.\n";
+
+
+  print "</PRE>\n";
+
+  &end_html;
+}
+
+#
+#
+sub print_images {
+  $web_subdir = pop( @_ );
+  $subdir = pop( @_ );
+  $newsgroup = pop( @_ );
+
+  opendir( SUBDIR, $subdir );
+
+  my $count = 0;
+
+  while( $_ = readdir( SUBDIR ) ) {
+    my $file = "$subdir/$_";
+    next if( ! -f $file || ! -r $file );
+    my $extension = $file;
+    $extension =~ s/^.*\.//;
+    $extension = "\L$extension";
+    
+    if( $extension eq "gif" || $extension eq "jpg" || $extension eq "jpeg" ) {
+      print "<CENTER> <IMG SRC=$base_address_for_files/queues/$newsgroup/$web_subdir/$_></CENTER><HR>\n";
+      $count++;
+    } else {
+      my $filename = $_;
+      $filename =~ s/^.*\///;
+      next if $filename eq "skeleton.skeleton" 
+             || $filename eq "headers.txt"
+              || $filename eq "full_message.txt"
+             || $filename eq "text.files.lst"
+             || $filename eq "stump-prolog.txt"
+             || $filename eq "stump-warning.txt"
+             || $filename =~ /msg-.*\.doc/;
+      
+      &print_image( "no_image.gif", "security warning" );
+      print "<B>Non-image attachment:</B><CODE>$filename</CODE> NOT SHOWN for security reasons.<BR>\n";
+    }
+  }
+  return $count;
+}
+
+# prints warning if there is warning stored about the article
+sub print_article_warning { # short-subdir
+  my $file = pop( @_ );
+
+  my $warning_file = &article_file_name( $file ) . "/stump-warning.txt";
+
+  if( -r $warning_file ) {
+    open( WARNING, $warning_file );
+    while ($warning = <WARNING>) {
+       next unless $warning =~ m/\S/;
+       $warning =~ s/\&/&amp;/g;
+       $warning =~ s/</&lt;/g;
+       $warning =~ s/>/&gt;/g;
+       &print_image( "star.gif", "warning" );
+       print "<FONT COLOR=red>$warning</FONT><br>\n";
+    }
+    close( WARNING );
+    return 1;
+  }
+
+  return 0;
+}
+
+sub get_queue_list ($) {
+    my ($newsgroup) = @_;
+    my $dir = "$queues_dir/$newsgroup";
+    my %sortkeys;
+
+    opendir(QUEUED, $dir) or &error("could not open directory $dir");
+
+    for (;;) {
+       $!=0;
+       my $subdir= scalar readdir(QUEUED);
+       last unless defined $subdir;
+
+       my $subpath= "$dir/$subdir";
+       next if $subdir =~ /^\.+/;
+       next unless -d $subpath;
+       my $sortkey;
+       if (!stat "$subpath/stump-warning.txt") {
+           $!==&ENOENT or die "$subpath $!";
+           $sortkey= 0;
+       } else {
+           $sortkey= (stat _)[9];
+       }
+       $sortkeys{$subdir}= $sortkey;
+    }
+    closedir( QUEUED );
+    my @articles= sort { $sortkeys{$a} <=> $sortkeys{$b} } keys %sortkeys;
+    return ($dir, @articles);
+}
+
+# main moderation page
+sub html_moderation_screen {
+  my $newsgroup = &required_parameter( 'newsgroup' );
+  my $moderator = $request{'moderator'};
+  my $password = $request{'password'};
+
+
+  if( $request{'next_screen'} eq 'single' ) {
+    # we show a single article if the user so requested.
+    # just get the first article from the queue if any, otherwise show 
+    # an empty main screen.
+   
+    my ($dir, @articles)= get_queue_list($newsgroup);
+
+    my $i;
+    for ($i=0; $i<@articles; $i++) {
+       my $subdir= shift @articles;
+       push @articles, $subdir;
+       last if $request{"decision_$subdir"};
+    }
+
+    while( $subdir = shift @articles ) {
+      if( -d "$dir/$subdir" && !($subdir =~ /^\.+/) 
+          && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
+             &html_moderate_article( $subdir );
+             return;
+      }
+    }
+  } else {
+       # otherwise just show the moderator an empty main screen.
+  }
+    
+  &begin_html( "Main Moderation Screen: $newsgroup" );
+  print "Welcome to the main moderation screen. Its main purpose is to 
+help you process most messages extremely quickly. For every message, it 
+presents you who sent it, as well as the first three non-blank lines.
+For those messages where the decision is obvious, simply select your
+decision (approve/reject etc) and click submit. For those messages which
+you would like to review in more details, do not select anything and
+use Review/Comment function from this screen or from a subsequent screen.
+Remember that if you do not make any decision, the article would stay in the
+queue.\n";
+
+  &read_rejection_reasons;
+
+  my ($dir, @articles)= get_queue_list($newsgroup);
+
+  print "
+  <FORM METHOD=$request_method action=$base_address>
+  <INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
+    &html_print_credentials;
+  
+  my $file, $subject = "No Subject", $from = "From nobody";
+  my $form_not_empty = "";
+  my $article_count = 0;
+  my $warning = "";
+  while( ($subdir = shift @articles) && $article_count++ < 40 ) {
+    $file=$subdir;
+    if( -d "$dir/$subdir" && !($subdir =~ /^\.+/) 
+        && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
+        while( <PROLOG> ) {
+          chop;
+          if( /^Real-Subject: /i ) {
+           s/\&/&amp;/g;
+            s/</&lt;/g;
+            s/>/&gt;/g;
+            s/^Real-Subject: //g;
+            $subject = substr( $_, 0, 50 );
+          } elsif( /^From: /i ){
+           s/\&/&amp;/g;
+            s/</&lt;/g;
+            s/>/&gt;/g;
+            $from = substr( $_, 0, 50 );
+          } elsif( /^$/ ) {
+            last;
+          }
+        }
+
+        print "<HR><B>$from: $subject</B>(";
+        print "<A HREF=$base_address?action=moderate_article&newsgroup=$newsgroup&" .
+              "moderator=$moderator&password=$password&file=$subdir>Review/Comment/Preapprove</A>)<BR>\n";
+        print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=approve>Approve\n";
+        print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=skip>Leave\n";
+        print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=leave>Back of queue\n";
+        foreach (@short_rejection_reasons) {
+          print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=\"reject $_\">Reject \u$_\n";
+        }
+
+       print "<BR>\n";
+
+        &print_article_warning( $file );
+
+        print "<PRE>\n";
+
+        my $i = 0;
+
+        while( ($_ = <PROLOG>) && $i < 5 ) {
+            chop;
+           next if m/^\>/;
+           s/\&/&amp;/g;
+            s/</&lt;/g;
+            s/>/&gt;/g;
+            if( $_ ne "" ) {
+              print "]  " . substr( $_, 0, 75 ) . "\n";
+              $i++;
+            }
+        }
+
+        print "</PRE>";
+        $form_not_empty = "yes";
+        close( PROLOG );
+        $article_count += &print_images( $newsgroup, "$dir/$subdir", $subdir );
+    }
+  }
+
+  if( $form_not_empty ) {
+    print "<HR> <INPUT TYPE=submit VALUE=Submit>
+<INPUT TYPE=reset VALUE=Reset>
+";
+  } else {
+    print "
+<HR>
+No articles present in the queue
+<INPUT TYPE=submit VALUE=Refresh>
+<HR>\n";
+  }
+
+  print "<A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
+        "moderator=$moderator&password=$password>Change Password</A>";
+
+  print "</FORM>\n\n";
+
+  print "<FORM METHOD=$request_method action=$base_address>";
+  &html_print_credentials;
+  print "<INPUT NAME=action VALUE=moderator_admin TYPE=hidden>
+         <INPUT TYPE=submit VALUE=\"Manage pass/grey/block-lists\">
+         </FORM>";
+
+  &end_html;
+}
+
+# prints hidden fields -- credentials
+sub html_print_credentials {
+  my $newsgroup = $request{'newsgroup'};
+  my $moderator = $request{'moderator'};
+  my $password = $request{'password'};
+
+  print "
+ <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
+ <INPUT NAME=moderator VALUE=\"$moderator\" TYPE=hidden>
+ <INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
+}
+
+# newsgroup admin page
+sub html_newsgroup_management {
+  &begin_html( "Administer $request{'newsgroup'}" );
+
+  print "All usernames and passwords are not case sensitive.\n";
+  print "<HR>Use this form to add new moderators or change passwords:<BR>
+ <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=add_user TYPE=hidden>";
+  &html_print_credentials;
+  print "
+ Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
+ <BR>
+ Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=\"Add/Change\">
+ <INPUT TYPE=reset VALUE=Reset>
+ </FORM>
+";
+
+  print "<HR>Use this form to delete moderators:<BR>
+ <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=delete_user TYPE=hidden>";
+  &html_print_credentials;
+  print "
+ Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=\"Delete Moderator\">
+ <INPUT TYPE=reset VALUE=Reset>
+ </FORM><HR>
+
+ <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=edit_list TYPE=hidden>";
+  &html_print_credentials;
+  print "
+  Configuration List: <SELECT NAME=list_to_edit>
+
+    <OPTION VALUE=good.posters.list>Good Posters List
+    <OPTION VALUE=watch.posters.list>Suspicious Posters List
+    <OPTION VALUE=bad.posters.list>Banned Posters List
+    <OPTION VALUE=good.subjects.list>Good Subjects List
+    <OPTION VALUE=watch.subjects.list>Suspicious Subjects List
+    <OPTION VALUE=bad.subjects.list>Banned Subjects List
+    <OPTION VALUE=watch.words.list>Suspicious Words List
+    <OPTION VALUE=bad.words.list>Banned Words List
+
+  </SELECT>
+  <INPUT TYPE=submit VALUE=\"Edit\">
+  <INPUT TYPE=reset VALUE=Reset>";
+
+  &link_to_help( "filter-lists", "filtering lists" );
+
+  print "
+  </FORM><HR>
+
+  List of current moderators:<P>
+
+  <UL>\n";
+
+  foreach (keys %moderators) {
+      print "<LI> $_\n";
+  }
+
+  print "</UL>\n";
+
+  &end_html;
+}
+
+
+# edit config list
+sub edit_configuration_list {
+
+  my $list_to_edit = &required_parameter( 'list_to_edit' );
+
+  $list_to_edit = &check_config_list( $list_to_edit );
+
+  my $list_file = &full_config_file_name( $list_to_edit );
+
+  my $list_content = "";
+
+  if( open( LIST, $list_file ) ) {
+    $list_content .= $_ while( <LIST> );
+    close( LIST );
+  }
+
+  $list_content =~ s/\&/&amp;/g;
+  $list_content =~ s/</&lt;/g;
+  $list_content =~ s/>/&gt/g;
+
+  &begin_html( "Edit $list_to_edit" );
+
+  print
+" <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=set_config_list TYPE=hidden>
+ <INPUT NAME=list_to_edit VALUE=$list_to_edit TYPE=hidden>";
+  &html_print_credentials;
+  &link_to_help( $list_to_edit, "$list_to_edit" );
+  print "
+ Edit this list: <HR>
+<TEXTAREA NAME=list rows=20 COLS=50>
+$list_content</TEXTAREA>
+
+ <BR>
+ <INPUT TYPE=submit VALUE=\"Set\">
+ </FORM>
+";
+
+  &end_html;
+}
+
+# password change page
+sub html_change_password{
+  &begin_html( "Change Password" );
+
+  print "All usernames and passwords are not case sensitive.\n";
+  print "<HR>Use this form to change your password:<BR>
+ <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=validate_change_password TYPE=hidden>";
+  &html_print_credentials;
+  print "
+ <BR>
+ New Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
+ <BR>
+ <INPUT TYPE=submit VALUE=Submit>
+ <INPUT TYPE=reset VALUE=Reset>
+ </FORM>
+";
+
+  &end_html;
+}
+
+
+# newsgroup creation form
+sub init_request_newsgroup_creation{
+  my $newsgroup = &required_parameter( 'newsgroup' );
+
+  &begin_html( "Request Creation of $newsgroup" );
+
+  print "This page helps you ask the system administrator of your domain
+to create <B>$newsgroup</B> on your server. Type in your domain name and
+click SUBMIT. An email will be sent to news\@domain and usenet\@domain
+and postmaster\@domain
+asking them to create your newsgroup. Please do NOT abuse this system.
+NOTE: You can give the URL of this page to your group readers so that 
+they could request creation of their newsgroups by themselves.\n";
+
+  print "<HR>
+ <FORM METHOD=$request_method action=$base_address>
+ <INPUT NAME=action VALUE=complete_newsgroup_creation_request TYPE=hidden>\n";
+  &html_print_credentials;
+  print "
+ <BR>
+ Domain Name ONLY: <INPUT NAME=domain_name VALUE=\"\" SIZE=40>
+ <BR>
+ <INPUT TYPE=submit VALUE=Submit>
+ <INPUT TYPE=reset VALUE=Reset>
+ </FORM>
+";
+
+  &end_html;
+}
+
+
+# newsgroup creation completion
+sub complete_newsgroup_creation_request{
+  my $newsgroup = &required_parameter( 'newsgroup' );
+  my $domain_name = &required_parameter( 'domain_name' );
+
+  if( !($domain_name =~ /(^[a-zA-Z0-9\.-_]+$)/) ) {
+    &user_error( "invalid domain name" );
+  }
+
+  $domain_name = $1;
+
+
+  my $request = "To: news\@$domain_name, usenet\@$domain_name, postmaster\@$domain_name
+Subject: Please create $newsgroup (Moderated)
+From: devnull\@algebra.com ($newsgroup Moderator)
+Organization: stump.algebra.com
+
+Dear News Administrator:
+
+A user of $domain_name has requested that you create a newsgroup
+
+       $newsgroup (Moderated) 
+
+on your server. $newsgroup
+is a legitimately created moderated newsgroup that is available worldwide.
+
+Thank you very much for your help and cooperation.
+
+Sincerely,
+
+       - Moderator of $newsgroup.
+
+";
+
+  &email_message( $request, "news\@$domain_name" );
+  &email_message( $request, "usenet\@$domain_name" );
+  &email_message( $request, "postmaster\@$domain_name" );
+
+  &begin_html( "Request to create $newsgroup sent" );
+
+  print "The following request has been sent:<HR><PRE>\n";
+
+  print "$request</PRE>\n";
+
+  &end_html;
+}
+
+# displays help
+sub display_help {
+  my $topic_name = &required_parameter( "topic" );
+
+  $topic_name =~ s/\///g;
+  $topic_name =~ s/\.\.//g;
+  $topic_name = &untaint( $topic_name );
+
+  my $file = "$webstump_home/doc/help/$topic_name.html";
+
+  &error( "Topic $topic_name not found in $file." ) 
+       if ! -r $file;
+
+  open( FILE, "$file" );
+  my $help = "";
+  $help .= $_ while( <FILE> );
+  close( FILE );
+
+  $help =~ s/##/$base_address?action=help&topic=/g;
+
+  &begin_html( "$topic_name" );
+
+  print $help;
+
+  print "<HR>";
+}
+
+
+
+
+
+
+
+1;
diff --git a/webstump/scripts/mime-parsing.lib b/webstump/scripts/mime-parsing.lib
new file mode 100644 (file)
index 0000000..4605f10
--- /dev/null
@@ -0,0 +1,177 @@
+#
+# this is a library of perl routines for MIME parsing.
+#
+
+if( $use_mime eq "yes" ){
+  require MIME::Parser;
+  require Convert::UU; import uudecode;
+}
+
+sub uudecode_text {
+  my $dir = pop( @_ );
+  my $entity = pop( @_ );
+
+  my $type = $entity->mime_type;
+  my $body = $entity->stringify_body;
+
+  if( $type =~ /^text\// ) {
+    my $filename = $entity->bodyhandle->path;
+    $filename =~ s/.*\///;
+    print TEXT_FILES $filename . "\n";
+
+    my $count = 0;
+    while(1) {
+      last if( $count++ > 15 );
+      my ($data, $name, $mode ) = &uudecode( $body );
+      $name =~ s/\//_/g;
+
+      if( $data && $name ) {
+        $body =~ s/\nbegin.*?\nend\n/((((Encoded File: $name))))\n/s;
+        if( open( FILE, ">$dir/$name" ) ) {
+          print FILE $data;
+          close FILE;
+          chmod 0644, $file;
+        }
+        my $filename = $entity->bodyhandle->path;
+        open( REDUCED, ">$filename" );
+        print REDUCED $body;
+        close( REDUCED );
+      } else {
+        last;
+      }
+    }
+  } else {
+    $body = "";
+  }
+
+  $body =~ s/\n+/\n/gs;
+
+  return $body;
+}
+
+sub decode_mime_message {
+  my $dir = pop( @_ );
+  mkdir $dir, 0775;
+  chmod 0755, $dir;
+  #chdir $dir;
+
+  # Create parser, and set the output directory:
+  my $parser = new MIME::Parser;
+  $parser->output_dir( $dir );
+
+  # Parse input:
+  $entity = $parser->read(\*STDIN) or die "couldn't parse MIME stream";
+
+  open( FULL, ">$dir/full_message.txt" );
+  print FULL $entity->as_string;
+  close( FULL );
+
+
+  my $RealSubject = "Real-Subject: " . $entity->head->get( "Subject" );
+
+  my $prolog = "From: " . $entity->head->get( "From" ) . "$RealSubject\n";
+  open( SKELETON, ">$dir/skeleton.skeleton" );
+  $entity->dump_skeleton( \*SKELETON ); 
+  close( SKELETON );
+
+  open( HEAD, ">$dir/headers.txt" );
+  print HEAD $entity->head->as_string . "\n";
+  close( HEAD );
+
+  open( TEXT_FILES, ">$dir/text.files.lst" );
+
+  print TEXT_FILES "headers.txt\n";
+
+  my $body = &uudecode_text( $entity, $dir );
+  $body =~ /(.*\n){0,3}/s;
+
+  $prolog .= $1;
+
+  if( $entity->is_multipart ) {
+    foreach( $entity->parts() ) {
+      print $_->mime_type . "\n";
+      $body = &uudecode_text( $_, $dir );
+      $body =~ /(.*\n){0,3}/s;
+      $prolog .= $1;
+    }
+  }
+
+  close( TEXT_FILES );
+
+  $Article_From = $entity->head->get( "From" );
+  chop $Article_From;
+  $Article_Subject = $entity->head->get( "Subject" );
+  chop $Article_Subject;
+  $Article_Head = $entity->head->as_string;
+  $Article_Body = $body;
+
+  chmod $dir, 0755;
+  return ($entity, $prolog);
+}
+
+sub decode_plaintext_message {
+  my $dir = pop( @_ );
+
+  $Article_Head = "";
+
+  while( <STDIN> ) {
+    $Article_Head .= $_;
+    chomp;
+    if( /^From: / ) {
+      $Article_From = $_;
+      $Article_From =~ s/^From: //;
+    } elsif( /^Subject: / ) {
+      $Article_Subject = $_;
+      $Article_Subject =~ s/^Subject: //;
+    }
+
+    last if /^$/;
+  }
+
+  $Article_Body = "";
+
+  $Article_Body .= $_ while( <STDIN> );
+
+  return &file_plaintext_message( $dir );
+
+}
+
+# stores a plaintext message in a fashion similar to a MIME message
+sub file_plaintext_message {
+  my $dir = pop( @_ );
+
+  mkdir $dir, 0775;
+  chmod 0755, $dir;
+
+  open( FULL, ">$dir/full_message.txt" );
+  print FULL $Article_Body;
+  close( FULL );
+
+
+  my $prolog = "From: " . $Article_From . "\nReal-Subject: $Article_Subject";
+#             . "Subject: " . $entity->head->get( "Subject" );
+
+  $prolog .= "\n\n";
+  open( SKELETON, ">$dir/skeleton.skeleton" );
+  close( SKELETON );
+
+  open( HEAD, ">$dir/headers.txt" );
+  print HEAD $Article_Head . "\n";
+  close( HEAD );
+
+  open( TEXT_FILES, ">$dir/text.files.lst" );
+
+  print TEXT_FILES "headers.txt\nfull_message.txt\n";
+
+  my $body = $Article_Body;
+  $body =~ /(.*\n){0,3}/s;
+
+  $prolog .= $1;
+
+  close( TEXT_FILES );
+
+  return $prolog;
+}
+1;
diff --git a/webstump/scripts/strip-ats.pl b/webstump/scripts/strip-ats.pl
new file mode 100755 (executable)
index 0000000..f4ab19f
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/perl
+
+$after_ats = 0;
+
+while( <STDIN> ) {
+
+  if( $after_ats ) {
+       print;
+  } elsif( /^\@\@\@/ ) {
+        $after_ats = 1;
+  }
+}
diff --git a/webstump/scripts/webstump.lib.pl b/webstump/scripts/webstump.lib.pl
new file mode 100644 (file)
index 0000000..8397f86
--- /dev/null
@@ -0,0 +1,733 @@
+# this is a collection of library functions for stump.
+
+use IO::Handle;
+
+# error message
+sub error {
+  my $msg = pop( @_ );
+
+  if( defined $html_mode ) {
+    print 
+"Content-Type: text/html\n\n
+<TITLE>WebSTUMP Error</TITLE>
+<BODY BGCOLOR=\"#C5C5FF\" BACKGROUND=$base_address_for_files/images/bg1.jpg>
+<H1>You have encountered an error in WebSTUMP.</H1>";
+
+  &print_image( "construction.gif", "bug in WebSTUMP" );
+
+  print " <B>$msg </B><HR>
+Please cut and paste this
+whole page and send it to <A HREF=mailto:$supporter>$supporter</A>.<P>
+Query Parameters:<P>\n
+<UL>";
+
+    foreach (keys %request) {
+      print "<LI> $_: $request{$_}\n";
+    }
+    exit 0;
+  }
+
+  die $msg;
+}
+
+# user error message
+sub user_error {
+  my $msg = pop( @_ );
+  if( defined $html_mode ) {
+    print 
+"Content-Type: text/html\n\n
+<TITLE>You have made a mistake.</TITLE>
+<BODY BGCOLOR=\"#C5C5FF\" BACKGROUND=$base_address_for_files/images/bg1.jpg>
+<H1>You have made a mistake.</H1>
+  ";
+
+  &print_image( "warning_big.gif", "Warning" );
+
+  print " <B>$msg </B><HR>
+Please go back to the previous page and correct it. If you get really
+stuck, cut and paste this whole page and send it to <A
+HREF=mailto:$supporter>$supporter</A>.
+
+";
+
+    exit 0;
+  }
+
+  die $msg;
+}
+
+# returns full config file name
+sub full_config_file_name {
+  my $short_name = pop( @_ );
+  my $newsgroup = &required_parameter( "newsgroup" );
+  $newsgroup =~ m/^\w[.0-9a-z+]+$/ or die;
+  $newsgroup= $&;
+  return  "$webstump_home/config/newsgroups/$newsgroup/$short_name";
+}
+
+# checks if the admin password supplied is correct
+sub verify_admin_password {
+
+  my $password = $request{'password'};
+
+  my $password_file = "$webstump_home/config/admin_password.txt";
+
+  open( PASSWORD, $password_file )
+        || &error( "Password file $password_file does not exist" );
+  my $correct_password = <PASSWORD>;
+  chomp $correct_password;
+  close( PASSWORD );
+
+  &user_error( "invalid admin password" )
+        if( $password ne $correct_password );
+
+}
+
+#
+# appends a string to file.
+#
+sub append_to_file {
+  my $msg = pop( @_ );
+  my $file = pop( @_ );
+
+  open_file_for_appending( FILE, "$file" ) 
+       || die "Could not open $file for writing";
+  print FILE $msg;
+  close( FILE );
+}
+
+#
+# add to config file
+sub add_to_config_file {
+  my $line = pop( @_ );
+  my $file = pop( @_ );
+
+print STDERR "File = $file, line= $line\n";
+
+  if( !&name_is_in_list( $line, $file ) ) {
+    &report_list_diff($file, sub {
+       print DIFF "Added: $line\n" or die $!;
+    });
+    &append_to_file( &full_config_file_name( $file ), "$line\n" );
+  }
+}
+
+
+sub report_list_diff ($$) {
+  my ($list_file, $innards) = @_;
+
+  my $head = &full_config_file_name( "change-notify-header" );
+  if (!open DHEAD, '<', $head) {
+      $!==&ENOENT or die "$head $!";
+      return;
+  }
+  my $diff = "$list_file.diff.$$.tmp";
+  my $ok= eval {
+      open DIFF, '>>', $diff or die "$diff $!";
+      while (<DHEAD>) { print DIFF or die $!; }
+      print DIFF <<END or die $!;
+
+Moderator: $request{'moderator'}
+Control file: $list_file
+
+END
+      DHEAD->error and die $!;
+      DIFF->flush or die $!;
+
+      my $goahead= &$innards($diff);
+
+      if ($goahead) {
+         print DIFF "\n-- \n" or die $!;
+         close DIFF or die $!;
+         my $child= fork; die unless defined $child;
+         if (!$child) {
+             open STDIN, '<', $diff or die "$diff $!";
+             exec find_sendmail(), qw(-odb -oem -oee -oi -t);
+             die $!;
+         }
+         waitpid($child,0) == $child or die "$list_file $!";
+      }
+      $?==0 or die "$list_file $?";
+      unlink $diff or die $!;
+      1;
+  };
+  if (!$ok) {
+      unlink $diff;
+      &error("Could not report change to $list_file: $@");
+  }
+}
+
+# from CGI.pm
+# unescape URL-encoded data
+sub unescape {
+    my $todecode = shift;
+    $todecode =~ tr/+/ /;       # pluses become spaces
+    $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
+    return $todecode;
+}
+# sets various useful variables, etc
+sub setup_variables {
+  $newsgroups_list_file = "$webstump_home/config/newsgroups.lst";
+}
+
+# initializes webstump, reads newsgroups list
+sub init_webstump {
+  &setup_variables;
+
+  # read the NG list
+  opendir( NEWSGROUPS, "$webstump_home/config/newsgroups" )
+       || &error( "can't open $webstump_home/config/newsgroups" );
+
+    while( $_ = readdir( NEWSGROUPS ) ) {
+      my $file = "$webstump_home/config/newsgroups/$_/address.txt";
+      my $ng = $_;
+
+      next if ! -r $file;
+
+      open( FILE, $file );
+      $addr = <FILE>;
+      chop $addr;
+      close( FILE );
+
+       &error( "Invalid entry $_ in the newsgroups database." )
+               if( !$ng || !$addr );
+        push @newsgroups_array,$ng;
+        $newsgroups_index{$ng} = "$addr";
+    }
+  close( NEWSGROUPS );
+
+  open( LOG, ">>$webstump_home/log/webstump.log" );
+  print LOG "Call from $ENV{'REMOTE_ADDR'}, QUERY_STRING=$ENV{'QUERY_STRING'}\n";
+}
+
+# gets the directory name for the newsgroup
+sub getQueueDir {
+  my $newsgroup = pop( @_ );
+  if( $newsgroups_index{$newsgroup} ) {
+    return "$queues_dir/$newsgroup";
+  } 
+  return ""; # undefined ng
+}
+
+# reads request, if any
+sub readWebRequest {
+  my @query;
+  my %result;
+  if( defined $ENV{"QUERY_STRING"} ) {
+
+    @query = split( /&/, $ENV{"QUERY_STRING"} );
+    foreach( @query ) {
+      my ($name, $value) = split( /=/ );
+      $result{&unescape($name)} = &unescape( $value );
+    }
+  }
+
+  while(<STDIN>) {
+    @query = split( /&/, $_ );
+    foreach( @query ) {
+      my ($name, $value) = split( /=/ );
+      $result{&unescape($name)} = &unescape( $value );
+    }
+  }
+
+  foreach( keys %result ) {
+    print LOG "Request: $_ = $result{$_}\n" if( $_ ne "password" );
+  }
+  return %result;
+}
+
+# Checks if the program is running in a demo mode
+sub is_demo_mode {
+  return &optional_parameter( 'newsgroup' ) eq "demo.newsgroup" 
+        && !$ignore_demo_mode;
+}
+
+# opens file for writing
+sub open_file_for_writing { # filehandle, filename
+  my $filename = pop( @_ );
+  my $filehandle = pop( @_ );
+
+  if( &is_demo_mode ) {
+       return( open( $filehandle, ">/dev/null" ) );  
+  } else {
+       return( open( $filehandle, ">$filename" ) );
+  }
+}
+
+# opens pipe for writing
+sub open_pipe_for_writing { # filehandle, filename
+  my $filename = pop( @_ );
+  my $filehandle = pop( @_ );
+
+  if( &is_demo_mode ) {
+       return( open( $filehandle, ">/dev/null" ) );  
+  } else {
+       return( open( $filehandle, "|$filename" ) );
+  }
+}
+
+# opens file for appending
+sub open_file_for_appending { # filehandle, filename
+  my $filename = pop( @_ );
+  my $filehandle = pop( @_ );
+
+  if( &is_demo_mode ) {
+       return( open( $filehandle, ">>/dev/null" ) );  
+  } else {
+       return( open( $filehandle, ">>$filename" ) );
+  }
+}
+
+# gets a parameter
+sub get_parameter {
+  my $arg = pop( @_ );
+  return "" if( ! defined $request{$arg} );
+  return $request{$arg};
+}
+
+# barfs if the required parameter is not supplied
+sub required_parameter {
+  my $arg = pop( @_ );
+  user_error( "Parameter \"$arg\" is not defined or is empty" )
+       if( ! defined $request{$arg} || !$request{$arg} );
+  return $request{$arg};
+}
+
+# optional request parameter
+sub optional_parameter {
+  my $arg = pop( @_ );
+  return $request{$arg};
+}
+
+# issues a security alert
+sub security_alert {
+  my $msg = pop( @_ );
+  print LOG "SECURITY_ALERT: $msg\n";
+}
+
+# reads the moderators info
+sub read_moderators {
+  my $newsgroup = &required_parameter( "newsgroup" );
+
+  my $file = &full_config_file_name( "moderators" );
+
+  open( MODERATORS, "$file" )
+        || error( "Could not open file with moderator passwords: $file" );
+  while( <MODERATORS> ) {
+    my ($name, $pwd) = split;
+    $moderators{"\U$name"} = "\U$pwd";
+  }
+  close( MODERATORS );
+}
+
+# saves the moderators info
+sub save_moderators {
+  my $newsgroup = &required_parameter( "newsgroup" );
+
+  my $file = &full_config_file_name( "moderators" );
+
+  open_file_for_writing( MODERATORS, $file );
+#        || &error( "Could not open file with moderator passwords: $file" );
+
+  foreach (keys %moderators) {
+      print MODERATORS "$_ $moderators{$_}\n";
+  }
+  close( MODERATORS );
+}
+
+# authenticates user
+sub authenticate {
+  my $password = &required_parameter( "password" );
+  my $moderator = &required_parameter( "moderator" );
+  my $newsgroup = &required_parameter( "newsgroup" );
+  
+  &read_moderators;
+
+  if( !defined $moderators{"\U$moderator"} || 
+      $moderators{"\U$moderator"} ne "\U$password" ) {
+    &security_alert( "Authentication denied." )
+    &user_error( "Authentication denied." );
+  }
+}
+
+# cleans request of dangerous characters
+sub disinfect_request {
+  if( defined $request{'newsgroup'} ) {
+    $newsgroup = $request{'newsgroup'};
+    $newsgroup =~ m/^(\w[.0-9a-z+]+)$/ or die;
+    $newsgroup= $1;
+    $request{'newsgroup'} = $newsgroup;
+  }
+
+  if( defined $request{'file'} ) {
+    my $file = $request{'file'};
+    $file =~ m/^\w[.0-9a-z]+\.list$|^dir_\d+_\d+$/ or die "$file ?";
+    $file = "$&";
+    $request{'file'} = $file;
+  }
+}
+
+# adds a user
+sub add_user {
+  my $user = &required_parameter( "user" );
+  my $new_password = &required_parameter( "new_password" );
+
+  &user_error( "Username may only contain letters and digits" )
+    if( ! ($user =~ /^[a-zA-Z0-9]+$/ ) );
+  &user_error( "Password may only contain letters and digits" )
+    if( ! ($new_password =~ /^[a-zA-Z0-9]+$/ ) );
+  &user_error( "Cannot change password for user admin" )
+    if( "\U$user" eq "ADMIN" );
+
+  $moderators{"\U$user"} = "\U$new_password";
+
+  &save_moderators;
+}
+
+# checks that a config list is in enumerated set of values. Returns 
+# untainted value
+sub check_config_list {
+  my $list_to_edit = pop( @_ );
+
+ &user_error( "invalid list name $list_to_edit" )
+    if( $list_to_edit ne "good.posters.list"
+        && $list_to_edit ne "watch.posters.list"
+        && $list_to_edit ne "bad.posters.list"
+        && $list_to_edit ne "good.subjects.list"
+        && $list_to_edit ne "watch.subjects.list"
+        && $list_to_edit ne "bad.subjects.list"
+        && $list_to_edit ne "bad.words.list"
+        && $list_to_edit ne "watch.words.list" );
+
+  return &untaint( $list_to_edit );
+}
+
+# sets a configuration list (good posters etc)
+sub set_config_list {
+  my $list_content = $request{"list"};
+  my $list_to_edit = &required_parameter( "list_to_edit" );
+
+  $list_content .= "\n";
+  $list_content =~ s/\r//g;
+  $list_content =~ s/\n+/\n/g;
+  $list_content =~ s/\n +/\n/g;
+  $list_content =~ s/^\n+//g;
+
+  $list_to_edit = &check_config_list( $list_to_edit );
+
+  my $list_file = &full_config_file_name( $list_to_edit );
+
+  open_file_for_writing( LIST, "$list_file.new" ) 
+    || &error( "Could not open $list_file for writing" );
+  print LIST $list_content;
+  close( LIST );
+
+  report_list_diff("$list_to_edit", sub {
+      my ($diff)= @_;
+      my $child= fork; die unless defined $child;
+      if (!$child) {
+         open STDOUT, '>&DIFF' or die $!;
+         exec 'diff','-u','-L', "$list_to_edit.old",'-L', "$list_to_edit.new",'--', "$list_file","$list_file.new";
+         die $!;
+      }
+      waitpid($child,0) == $child or die "$list_file $!";
+      $?==0 or $?==256 or die "$list_file $?";
+      return !!$?;
+  });
+  rename ("$list_file.new", "$list_file");
+}
+
+# deletes a user
+sub delete_user {
+  my $user = &required_parameter( "user" );
+
+  &user_error( "User \U$user" . " does not exist!" ) 
+    if( ! defined $moderators{"\U$user"} );
+  &user_error( "Cannot delete user admin" )
+    if( "\U$user" eq "ADMIN" );
+
+  delete $moderators{"\U$user"};
+
+  &save_moderators;
+}
+
+# validate password change
+sub validate_change_password {
+  my $user = &required_parameter( "moderator" );
+  my $new_password = &required_parameter( "new_password" );
+
+  &user_error( "Password may only contain letters and digits" )
+    if( ! ($new_password =~ /^[a-zA-Z0-9]+$/ ) );
+  &user_error( "Cannot change password for user admin" )
+    if( "\U$user" eq "ADMIN" );
+
+  $moderators{"\U$user"} = "\U$new_password";
+
+  &save_moderators;
+  &html_welcome_page;
+}
+
+# reads rejection reasons
+sub read_rejection_reasons {
+  my $newsgroup = &required_parameter( 'newsgroup' );
+  my $reasons = &full_config_file_name( "rejection-reasons" );
+  open( REASONS, $reasons ) || &error( "Could not open file $reasons" );
+  while( <REASONS> ) {
+       chop;
+       my ($name, $title) = split( /::/ );
+       $rejection_reasons{$name} = $title;
+        push @short_rejection_reasons, $name;
+  }
+
+  close REASONS;
+}
+
+sub find_sendmail {
+
+  my $sendmail = "";
+
+  foreach (@sendmail) {
+    if( -x $_ ) {
+      $sendmail = $_;
+      last;
+    }
+  }
+  &error( "Sendmail not found" ) if( !$sendmail );
+
+  return $sendmail;
+}
+
+# email_message message recipient
+sub email_message {
+  my $recipient = pop( @_ );
+  my $message = pop( @_ );
+  my $sendmail= find_sendmail;
+  my $sendmail_command = "$sendmail $recipient";
+  $sendmail_command =~ /(^.*$)/; 
+  $sendmail_command = $1; # untaint
+  open_pipe_for_writing( SENDMAIL, "$sendmail_command > /dev/null " );
+  print SENDMAIL $message;
+  close( SENDMAIL );
+                
+}
+
+sub article_file_name {
+  my $file = pop( @_ );
+  return "$queues_dir/$newsgroup/$file";
+}
+
+sub untaint {
+  $arg = pop( @_ );
+  $arg =~ /(^.*$)/;
+  return $1;
+}
+
+sub rmdir_rf {
+  my $dir = pop( @_ );
+
+  return if &is_demo_mode;
+
+  opendir( DIR, $dir ) || return;
+  while( $_ = readdir(DIR) ) {
+    unlink &untaint( "$dir/$_" );
+  }
+  closedir( DIR );
+  rmdir( $dir );
+}
+
+sub approval_decision {
+  $newsgroup = &required_parameter( 'newsgroup' );
+  my $comment = &get_parameter( 'comment' );
+  my $decision = "";
+
+  my $poster_decision = &optional_parameter( "poster_decision" );
+  my $thread_decision = &optional_parameter( "thread_decision" );
+  
+  foreach( keys %request ) {
+    if( /^decision_(dir_[0-9a-z_]+)$/ ) {
+      $decision = $request{$&};
+      my $file= $1; # untainted
+
+      next if $request{'skip_submit'};
+      next if $decision eq 'skip';
+
+      my $waf= &article_file_name($1).'/stump-warning.txt';
+      if ($decision eq 'leave') {
+         my $now= time;  defined $now or die $!;
+         utime $now,$now, $waf or $!==&ENOENT or die "$waf $!";
+         next;
+      }
+
+      if ($decision eq 'consider') {
+         if (!open ADDWARN, '>>', $waf) {
+             $!==&ENOENT or die "$waf $!";
+         } else {
+             print ADDWARN "A moderator has marked this message for further consideration - please consult your comoderators before approving.\n" or die $!;
+             close ADDWARN or die $!;
+         }
+         next;
+      }
+
+      die "$decision ?" unless $decision =~ m/^(approve|reject \w+)$/;
+      $decision= $1;
+
+      my $fullpath = &article_file_name( $file ) . "/stump-prolog.txt";
+
+      $decision = "reject thread" if $thread_decision eq "ban";
+      $decision = "approve" if $thread_decision eq "preapprove";
+
+      $decision = "reject abuse" if $poster_decision eq "ban";
+      $decision = "approve" if $poster_decision eq "preapprove";
+
+      if( -r $fullpath && open( MESSAGE, "$fullpath" ) ) {
+        my $RealSubject = "", $From = "", $Subject = "";
+        while( <MESSAGE> ) {
+          if( /^Subject: /i ) {
+           chop;
+            $Subject = $_;
+           $Subject =~ s/Subject: +//i;
+          } elsif( /^Real-Subject: /i ) {
+           chop;
+            $RealSubject = $_;
+           $RealSubject =~ s/Real-Subject: +//i;
+           $RealSubject =~ s/Re: +//i;
+          } elsif( /^From: / ) {
+           chop;
+            $From = $_;
+           $From =~ s/From: //i;
+          }
+          last if /^$/;
+        }
+        close MESSAGE;
+
+        &add_to_config_file( "good.posters.list", $From ) 
+               if $poster_decision eq "preapprove";
+
+        &add_to_config_file( "good.subjects.list", $RealSubject ) 
+               if $thread_decision eq "preapprove";
+
+        &add_to_config_file( "watch.posters.list", $From ) 
+               if $poster_decision eq "suspicious";
+
+        &add_to_config_file( "bad.posters.list", $From ) 
+               if $poster_decision eq "ban";
+
+        &add_to_config_file( "bad.subjects.list", $RealSubject ) 
+               if $thread_decision eq "ban";
+
+        &add_to_config_file( "watch.subjects.list", $RealSubject ) 
+               if $thread_decision eq "watch";
+
+# Subject, newsgroup, ShortDirectoryName, decision, comment
+        &process_approval_decision( $Subject, $newsgroup, $file, $decision, $comment );
+
+      }
+    }
+  }
+
+  &html_moderation_screen;
+}
+
+# gets the count of unapproved articles sitting in the queue
+sub get_article_count {
+  my $newsgroup = pop( @_ );
+   my $count = 0;
+   my $dir = &getQueueDir( $newsgroup );
+   opendir( DIR, $dir );
+   my $file;
+   while( $file = readdir( DIR ) ) {
+     $count++ if( -d "$dir/$file" && $file ne "." && $file ne ".." && -r "$dir/$file/full_message.txt" );
+   }
+
+   return $count;
+}
+
+# processes web request
+sub processWebRequest {
+
+  my $action = $request{'action'};
+  my $newsgroup = $request{'newsgroup'};
+  my $moderator = $request{'moderator'};
+  my $password = $request{'password'};
+
+  $moderator = "\L$moderator";
+
+  if( $action eq "login_screen" ) {
+    &html_login_screen;
+  } elsif( $action eq "moderation_screen" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    if( $moderator eq "admin" ) {
+      &html_newsgroup_management;
+    } else {
+      &html_moderation_screen;
+    }
+  } elsif( $action eq "moderator_admin" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    &html_newsgroup_management;
+  } elsif( $action eq "edit_list" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    &edit_configuration_list;
+  } elsif( $action eq "add_user" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    if( $moderator ne "admin" ) {
+      &security_alert( "Moderator $moderator tried to add user in $newsgroup" );
+      &user_error( "Only administrator (login ADMIN) can add or delete users" );
+    }
+
+    &add_user;
+    &html_newsgroup_management;
+  } elsif( $action eq "set_config_list" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    &set_config_list;
+    &html_newsgroup_management;
+  } elsif( $action eq "delete_user" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    if( $moderator ne "admin" ) {
+      &security_alert( "Moderator $moderator tried to add user in $newsgroup" );
+      &user_error( "Only administrator (login ADMIN) can add or delete users" );
+    }
+    &delete_user;
+    &html_newsgroup_management;
+  } elsif( $action eq "approval_decision" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    if( $moderator eq "admin" ) {
+      &user_error( "Login ADMIN exists for user management only" );
+    }
+    &approval_decision;
+  } elsif( $action eq "moderate_article" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    if( $moderator eq "admin" ) {
+      &user_error( "Login ADMIN exists for user management only" );
+    }
+    &html_moderate_article();
+  } elsif( $action eq "change_password" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    &html_change_password;
+  } elsif( $action eq "validate_change_password" ) {
+    &authenticate( $newsgroup, $moderator, $password );
+    &validate_change_password;
+#  } elsif( $action eq "init_request_newsgroup_creation" ) {
+#    &init_request_newsgroup_creation;
+#  } elsif( $action eq "complete_newsgroup_creation_request" ) {
+#    &complete_newsgroup_creation_request;
+  } elsif( $action eq "webstump_admin_screen" ) {
+    &webstump_admin_screen;
+  } elsif( $action eq "admin_login" ) {
+    &admin_login_screen;
+  } elsif( $action eq "admin_add_newsgroup" ) {
+    &admin_add_newsgroup;
+  } elsif( $action eq "help" ) {
+    &display_help;
+  } else {
+    &error( "Unknown user action: '$action'" );
+  }
+}
+
+
+1;
diff --git a/webstump/scripts/webstump.pl b/webstump/scripts/webstump.pl
new file mode 100755 (executable)
index 0000000..97b09ed
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+#
+# This is the main webstump cgi script.
+#
+# Figure out the home directory
+#
+
+if( !($0 =~ /\/scripts\/webstump\.pl$/) ) {
+  die "This script can only be called with full path name!!!";
+}
+
+$webstump_home = $0;
+$webstump_home =~ s/\/scripts\/webstump\.pl$//;
+
+$webstump_home =~ /(^.*$)/;
+$webstump_home = $1;
+
+require "$webstump_home/config/webstump.cfg";
+require "$webstump_home/scripts/webstump.lib.pl";
+require "$webstump_home/scripts/filter.lib.pl";
+require "$webstump_home/scripts/html_output.pl";
+#require "$webstump_home/scripts/gatekeeper.lib";
+require "$webstump_home/scripts/mime-parsing.lib";
+
+$html_mode = "yes";
+
+&init_webstump;
+
+######################################################################
+
+%request = &readWebRequest;
+
+$command = "";
+
+if( defined %request ) {
+  &disinfect_request;
+  $command = $request{'action'} if( defined $request{'action'} );
+}
+
+if( ! $command ) {
+  &html_welcome_page;
+} else {
+  &processWebRequest( $command );
+}
diff --git a/webstump/src/Makefile b/webstump/src/Makefile
new file mode 100644 (file)
index 0000000..7646390
--- /dev/null
@@ -0,0 +1,8 @@
+
+all: ../bin/wrapper
+
+../bin/wrapper: wrapper.c
+       $(CC) -o $@ -DWEBSTUMP_HOME=\"$(WEBSTUMP_HOME)\" wrapper.c
+       chmod 755 $@
+       #chmod u+s $@
+       ls -l $@
diff --git a/webstump/src/wrapper.c b/webstump/src/wrapper.c
new file mode 100644 (file)
index 0000000..a71bdb2
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * WebSTUMP wrapper. You have to compile this program using "make" and
+ * make sure that it is installed under ../bin. It should be set up as 
+ * setuid your user id. Directory referred to by webstump_home should
+ * exist and belong to the effective user id or the program will refuse
+ * to run.
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+const char * webstump_home = WEBSTUMP_HOME;
+
+const char * script_name = "scripts/webstump.pl";
+
+const char *safe_env[] = {
+       "SERVER_SOFTWARE",
+       "SERVER_NAME",
+       "GATEWAY_INTERFACE",
+       "SERVER_PROTOCOL",
+       "SERVER_PORT",
+       "REQUEST_METHOD",
+       "HTTP_ACCEPT",
+       "PATH_INFO",
+       "PATH_TRANSLATED",
+       "SCRIPT_NAME",
+       "QUERY_STRING",
+       "REMOTE_HOST",
+       "REMOTE_ADDR",
+       "REMOTE_USER",
+       "AUTH_TYPE",
+       "CONTENT_TYPE",
+       "CONTENT_LENGTH",
+       NULL
+};
+
+void cgi_error( const char * buf );
+
+/* Wrapper code. Argc and argv are ignored, except fot the list of 
+ * predefined variables.
+ */
+
+int main( int argc, char * argv[] ) /* argv is ignored */
+{
+  char * new_env[ 1000 ];       /* new environment */
+  char * new_argv[] = { NULL }; /* no arguments    */
+  char script_file_name[ 1024 ];
+  char buf[1024];
+  int i, new_env_i;
+  struct stat stat_buf;
+
+  for( i = 0, new_env_i = 0; safe_env[i] != NULL; i++ )
+  {
+    char * var;
+    if( (var = getenv( safe_env[i] )) != NULL ) {
+      char * new_var = malloc( strlen( safe_env[i] ) + 1 + strlen( var ) + 1 );
+      if( new_var != NULL )
+       {
+         strcpy( new_var, safe_env[i] );
+         strcat( new_var, "=" );
+         strcat( new_var, var );
+         new_env[ new_env_i++ ] = new_var;
+       }
+    }
+  }
+
+  new_env[new_env_i] = NULL;
+
+  /* check existence and ownership of the perl script */
+
+  strcpy( script_file_name, webstump_home );
+  strcat( script_file_name, "/" );
+  strcat( script_file_name, script_name );
+
+  if( stat( script_file_name, & stat_buf ) != 0 )
+    {
+      sprintf( buf, "Could not access file %s to check permissions.",
+               script_file_name );
+      cgi_error( buf );
+      exit( 0 );
+    }
+
+  if( stat_buf.st_uid != geteuid() )
+    {
+      sprintf( buf, "Security violation: file %s \n"
+                    "belongs to a different user than my effective user id.",
+               script_file_name );
+      cgi_error( buf );
+      exit( 0 );
+    }
+
+  if( stat_buf.st_mode & (0 | 02) ) /* group or world writable */
+    {
+printf( "File mode = %o, compared to %o\n", stat_buf.st_mode, (020 | 02) );
+      sprintf( buf, "Security violation: file %s \n"
+                    "is group or world writable.",
+               script_file_name );
+      cgi_error( buf );
+      exit( 0 );
+    }
+
+  execve( script_file_name, new_argv, new_env );
+
+  /* We can only be here if it could not be executed */
+
+  sprintf( buf, "Error: could not execute file %s", script_file_name );
+  cgi_error( buf );
+}
+
+
+void cgi_error( const char * buf )
+{
+  printf( 
+"Content-Type: text/html\n\n"
+"<TITLE>WebSTUMP Error</TITLE>\n"
+"<H1>WebSTUMP Error</H1>\n"
+"%s\n\n", 
+       buf );
+}
diff --git a/webstump/test/mm-test.pl b/webstump/test/mm-test.pl
new file mode 100755 (executable)
index 0000000..01bbd9d
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/perl
+
+require "../scripts/mime-parsing.lib";
+
+decode_mime_message( "q" );
diff --git a/webstump/tmp/demo.happy99.txt b/webstump/tmp/demo.happy99.txt
new file mode 100644 (file)
index 0000000..2b83a61
--- /dev/null
@@ -0,0 +1,569 @@
+From ichudov
+From: devnull
+Subject: Negation: The truth about AntiOnline::JHEKEYTGWJH
+X-Moderate-For: demo.newsgroup
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+Return-Path: <test-user@aol.com>
+Date: Thu, 20 May 1999 15:22:29 +0200 (CEST)
+Message-Id: <199905201322.PAA19186@mail.replay.com>
+From: loser@my-dejanews.com (Andrew Johnson)
+Newsgroups: demo.newsgroup
+Subject: Happy 1999!!!
+
+Happy 99! Just run this program and see for yourself:)
+
+begin 600 HAPPY99.EXE
+M1G)O;2!I8VAU9&]V"D9R;VTZ(&1E=FYU;&P*4W5B:F5C=#H@;6%R:6%N;F$G
+M<R!P:6,Z(&UA<FEA;FYA+FIP9SHZ2DA%2T595$=72D@*6"U-;V1E<F%T92U&
+M;W(Z('1E<W0N;F5W<V=R;W5P"@I`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`
+M0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`0$!`
+M"D9R;VT@:6-H=61O=B`@5V5D($UA>2`R-B`Q,#HS.3HU.2`Q.3DY"E)E='5R
+M;BU0871H.B`\:6-H=61O=CX*4F5C96EV960Z(&)Y(&UE('=I=&@@:60@2T%!
+M,#8Q,SD@9F]R(&EC:'5D;W8[(%=E9"P@,C8@36%Y(#$Y.3D@,3`Z,SDZ-3D@
+M+3`U,#`*365S<V%G92U)9#H@/#$Y.3DP-3(V,34S.2Y+04$P-C$S.4!M86YI
+M9F]L9"YA;&=E8G)A+F-O;3X*4W5B:F5C=#H@;6%R:6%N;F$G<R!P:6,Z(&UA
+M<FEA;FYA+FIP9PI4;SH@:6-H=61O=D!M86YI9F]L9"YA;&=E8G)A+F-O;2`H
+M26=O<B!#:'5D;W8@0"!H;VUE*0I$871E.B!7960L(#(V($UA>2`Q.3DY(#$P
+M.C,Y.C4X("TP-3`P("A#1%0I"E)E<&QY+51O.B!I8VAU9&]V0$%L9V5B<F$N
+M0V]M("A)9V]R($-H=61O=BD*1G)O;3H@:6-H=61O=E%144!!;&=E8G)A+D-O
+M;2`H26=O<B!#:'5D;W8@0"!H;VUE*0I8+4YO+4%R8VAI=F4Z('EE<PI/<F=A
+M;FEZ871I;VXZ($)O;VP@4VAE970@4V]F='=A<F4*6"U-86EL97(Z($5,32!;
+M=F5R<VEO;B`R+C4@4$PP<')E.%T*34E-12U697)S:6]N.B`Q+C`*0V]N=&5N
+M="U4>7!E.B!T97AT+W!L86EN.R!C:&%R<V5T/75S+6%S8VEI"D-O;G1E;G0M
+M5')A;G-F97(M16YC;V1I;F<Z(#=B:70*4W1A='5S.B!/"@I4:&ES(&ES(&$@
+M=&5S="!M97-S86=E+@H*270@:&%S(&$@<')E9F%C92!A;F0@82!U=65N8V]D
+M960@<&EC='5R92!O9B!-<RX@36%R:6%N;F$@4&]P;W9A+@H*271S('!U<G!O
+M<V4@:7,@=&\@=&5S="!796)35%5-4"X*"DAE<F4@9V]E<SH*"F)E9VEN(#8P
+M,"!M87)I86YN82YJ<&<*35]=0U]88&`P,D0Y*3%`8"%@,&!@8#!@(6!@(U]?
+M0"$H,%4I)3`U,2\T0TA`-B4X0#5&-5(\5D5/.T)@4PI-*T,D4#@R8$`T1C56
+M+D)@42Q"7%(N,EQ9+2)@0#0W-4$[)D54/C)@72@C/%4K(B$S.S9=3STF04D[
+M1CQ`"DTO,F!0(D]?.V`D+&`B8#@F(5`X)2)@/"<A4$0I(F!(+"5@5"PB4$PL
+M)C$H,R-1,#TF05P^)S%(/"<B8$0*32M"/$`H0E!#)R%02"U21$PL(R14+2,P
+M/RE31%TN(RA<*T,L5"Q/7SM@)"PA(C!$*2-@3"PF8%0M)B,H00I-)R(D4BQ#
+M*%(L0RA2+$,H4BQ#*%(L0RA2+$,H4BQ#*%(L0RA2+$,H4BQ#*%(L0RA2+$,H
+M4BQ#*%(L0RA2"DTL0RA2+$,H4BQ/7V!@(20H8#<H8%I0+"$H0&`B)#`D(R0P
+M)U]18&`_8&!@(2$P)"%@,"0A8#!@8&!@8&`*36!@8&!@,"@C(6`T)B%00"DB
+M0$]?46`B521@8")@,"PC8$`P(R$P-"0A8&!@8#=4(6!`+&`A(20E)$(D40I-
+M,#`X,S0V)"<H1R0T+$@F,4@P0$,P2R<A)34K,5PB,%,X1RHB(C!(-B510#DF
+M0C1&*5)`22I#,%4M0SQ8"DTN,TDC,20U)C%402DR12TT-34Y-S8E13HX5C%%
+M.48]2#HV25,])S56/5=!63Y(+B1!.#HG0BA&*D1)+C0*344Y.C=&*48Z2$HN
+M1$DZ.D=**D9*3$LN5$T[.E=.*T9:4$PO)%$\.R=2+$<J5$TO-%4].S=6+4<Z
+M6#XK0PI-62XW1EE>0TE:3R=27%\S55U//UA>/TM?46!@/V`P8"-@,"0A8#`D
+M(6`P)"%@8&!@8&!@8&`P*",A8#0F"DTA4$`I(D!/7U%@(E4D,&`B8#`H)"%@
+M+"0A4#0D(6!@(6!'/&!@,"@C)#`P)2@S)"8D1"4Q(58E4212*%(*34`P0#0P
+M229!3#PD*2A3+3)<(35"/$TD*B5",%18,C=1)5%`.29".$<J(D1*+3,X5RXC
+M1%HP5#$E,40]*`I-,C1),S4E-38U54$Y-D8M1#DV.4<Z)D5*/%<Q53U'/5@^
+M-THB0%@R)4%(/BA".$HR1%DR-45)/CA&.4I""DU(6C)%24H^2$HZ2E),6S)5
+M34L^6$X[2R)07#,E44P_*%(\2S)473,U54T_.%8]2T)87C-%64X_2%H^2U(*
+M35Q?,U5=3S]87C]+7U9`8"Q@4"1@8$$D(R0P8%]@+S(E7$U(55<Q2UU77ELU
+M/BXF)4\L7C4_73A75SH])PI-/#].6%U4+$)?*S0\/54C7EQ7+4U?/C]1*S97
+M*CA<4%M;+TU.+T5$.3567SU62E4M1#8Z+4@]2S!-7%=="DU7.U\C-$)-)E55
+M7$LK7TI?6#9?5DH]*5U:)UE/7C9%*#$C+RDL34Y66E<W7V`H(S-&3B5>.STB
+M4E]0(SL*32Y',U]@+R=/*5].5%E/6$HB0B8B6D%$04->.B\]34A`.STG7V!@
+M35Q3/UQ@2#4D,DI53R=.-UXG7ELT+`I-/4\[6S962TU?/BU=5UD_6$H_*B$L
+M2S\M+5U77V`E1UU61EM/5U1'4E]0(59(-4$Y-D964S(K7%=>73,_"DU=*#8Z
+M.UU9)E=22UU9.BXT1#TI33I8.38W7T5&3UPM,$TB3U9#.R93*UY;7V`H-D@Z
+M,CD[04U4+EM=5U\*36!@33,V3B4V6#=3*%DE7SU?4"%77SI(63!904LK/"L\
+M*TU.+UXV/U<V-DA61$Y%3B5>-BXS75=?8&!-30I-2C-;,2)542=>7#4_5SL_
+M6RTC/UP_)#Y53UXV.S]>4E1`*#9/*2]-(EXY)RQ/6T]?8"=*.UU,0UY4+5Y<
+M"DU67TY?6$]%2C%/7&!#6#=;3UY+2BU-2S<C.T57/TY:8"<K*%,S,CLY+5);
+M-4A#1D1<4CM93UA/7V`F-DH*35LV74]=2$1?/5LV5DM=5UDZ.R<B4E(R/C<\
+M,BM<5UPW4S]05"4E0BXQ1C5-4RU=64I!-4DO3#]25S503PI-6T]<8%E:+3,X
+M/TTR6T]<8#52/RQ77E,T.E,R*SA?+3M-7TI?6#]&2"DK-$1%6$PZ6SM#/5Q2
+M7SXV229."DTK23DH750^6UE&7ELI7TQ5-T1.2#U*6TU4/U,K75D]3UPU,SXX
+M3$1$/E8Q-EY=7R4R*BE%52%/+E\^/CT*32=<3UPU-B@K6U,F1%97-EM93UM5
+M-5A=5T<M7TY+-$0\/#L^.4Y#-4]&.UXF0")83%1#,$LI64,[1E9?40I--3A#
+M35E)*2U*4C(_/5Y=644Y3#U/(UU'0U9;2%].7ST[.S9).S%-65,[.4966SHX
+M8%5+*$]%2E51*5Q3"DT[/T4Y2CTI.UXW(E,^/2E<3U,_/DA'0S==4TY&1#M=
+M65\^2BX^)B];+RE<3EM9/UU:0"(Q-4%#-UE)22\*348W7BHR2D)/(E`I7%%.
+M,C]81ETZ3$LG)UQ/6T4_7EXZ0255*5-=6TH_651..")1)U]*6%]=5DA57U`B
+M-@I-5ED_7C9'759&45\Z+RE#7C8M3T5)3#Q$0DTM3DU67EU?(E4L(51$+"LW
+M-U,Q2UU62%DK-B]=4TTZ,3]7"DU'7"LT64M!-4XE5RLH3UM/7ELQ*3PP35D_
+M6U$_73E?)35$0SE8.25-62U-4%=7/UA%25=>1#]<8#,N,U\*36`L/4E42RI5
+M3RE-.UXG7ETT1E5*0")+(2DL3"P^6E=?8"@_6#9*+BI:-SI66BXQ/U=#/U!?
+M4")5-4(C7PI-8"4L/UY;,SA&7C5/7DY#-&`P2SPV7T8S/T\E-UE/7&!6-D<_
+M*E141E5/6#5)5E4V.CL\3UPW75=?.4I."DU53SM?8"9",UU62DU--UM/4E]7
+M2F`I)E]87B9?6EE?7E,U)E,M74@W7TM'7V`G2D,Z5U]404\K1D0W75<*35\^
+M24LQ5R=603U,4E]0(DM?0C9+8#TH2DT\*TXW7T5&5UPO7ELU/4L^5UY4,5LU
+M5E].5EY:5E=>6S1#+0I-/"L\,5\K)E=;34].35].52945$)7)#Y:5T1>6E=7
+M/T9?3E1@+3HE3RXW4EE))5\]35U;64I(6D5*)$9%"DU133TS.SDH7RM>6DI?
+M+3512T(G6S!.6SQ/6T]>*3]<8#U+)5PT,C%2.C,I63Q"35Q/7"LU0S9%*BP^
+M.$X*34,F+$D\235`7#XZ+RDL4S(R,C!;34]714LR34\D)D17-$1'15=4*SM&
+M7C=93UU&3R5;73\I3B9?5D]&2@I-1E1'4S\J4S5')DP])$PO)%Y`*SHZ)C$V
+M5E,J5U,M7R4P3UQ@45A?74Q:7"-!3TM"5SM=4SPR,4U?3$U-"DU+,D!<-3I5
+M.DM-0T]))C5/6U=3-3=-0C]*7$-7(C-?-4M.7T\K,2DC(E14/EHE/UA/6#I<
+M1TU/0BE+(E\*32LL3"Q26T]814LZ0%XI1EHR+ULS25]7/UX^/$4G34A$7S5:
+M05=4/4Y*4RU9/$1"7RI/7&`E-4(I.45<4PI-.RQ+/RQ75T5+1%LS6$`Z*RTH
+M4S,^/2-<2DM<4EM*5DLK42U(/5%.7$\T*#57+%=73T5*3SHQ1#-;+C0_"DU,
+M1E$F5%)67#];339>6UU52E1+.CE/+CL](RE=5DQ6(EI-64LV+U);0BU/3E\]
+M.DTR5CM=7%=3/U!44B$*351<1$=;3SLC7U!@32]81DHF/4M#4CDO14,W63];
+M53,W+$0Z2R=.1#4_5T)5(R\\,%XS*5Y>-UM/75I&8`I-63]-)E4_5T0_5S];
+M334D0T1>.UU<5U=67T%=4S1</5$C34->.49>-UY;-25;0B]3)U\\4C\^+U]@
+M(E-="DU:8"TZ)U\T45]0(ELS.%U66CM?/E]0(SDZ.R,C*$PL/E98.UM/7#)4
+M5BT[039&7SXQ35Q775=?.4E$1$,*33]</R)?4");,SE%-4\K5RM?4&!-+UM-
+M+3E+03M!/U<\.S\J5UPU+45&1%\]6TL]3UTY7R,P8#-67"LV7`I-1EHM/TY4
+M/SDX75=27CA/7EI5)%U03U8^,SQ,0E\K7SU)4E<E3E9775A/5S]82DPB3R$C
+M*$PL/E98.U]`"DTR5%8E.T$]5UM2+4]70S]052T[,C%-.U%;.24[7T`U+D(W
+M7S8_+5]25#I`(DY+-R)2,S].-3M93U@V7U8*33I#.E@Y.T(S/2,H3U)+7TT_
+M5TI-*D];4CM?/E]=14HO7T581%]0(54_7SE*0")*55$C74I>.UD_5S]<8`I-
+M)$]<8$TT/CTB55!.5C$_73=?/DHY3UQ@0UA>6U]08$Q?4"-6.D@Y*#A63B57
+M,4M=54]8.F`A3UXO0B]9"DU/6#9?745*+4U75D(O7ST[7U`A14E,14LC74A#
+M7C<[7$L_/3L[7SU*+4L]3TTD/T53,RM>6SM>*SU?/4D*34`R+4Y>5"M?8"T\
+M5DLA7#8J2EHR4RI*6TU-.R,Q5R)7(EL[039?/4U=63I17R1*4RU)/$=&.SU.
+M5UXF3`I-/#%?(T0Z6#]>*21</5551C8R)SM?/4LG7%E%2SI54B]=62=..DP]
+M2#]=2DPZ+U!'.S193CA#7%M?.4HQ"DTY15Y;-#]$33,_*SI*63A"75HI.C98
+M5DM=6DLD/5!/75DZ4SU+-3A#3%E)+TY1338\2CPR6%)$.B)3*E<*35PN6DE(
+M625?5D5+*T-,6T%/74=?8"@E-4$[)E@W6U1"2S8T2CP_64,Z+$D_4D95(3PR
+M,4U.049$-4I=(@I-7B<M53\W5DLS*SQ7-$153R,[7STY3UA-53XU45!3*D]>
+M3C=?8"<]33Y&/R,B+C(N6$\F0U9;1D$W/4Y?"DU72C4I/$TQ/EPQ-U]@*#Q/
+M/2\S23A8538O759/7DPW7B9',C]:1C,Z2E\]2DLM6595/U="7U`B3#=>)DD*
+M32DH62\K1%<S+5U77B4Z72A<3$9#.4\K0U]0(59*)$9;4S=?5T-?+DE86#]7
+M/#Y:.C%/15]/-3%%04-<50I-3T9$6E]0(R\R0B@F621/6T8O7&!7/UM5+4,W
+M64E/1E]"7UQ@.3H[)3I07C,G33<[7$]<8")5+4---35)"DU-3$1"7RU?/E]0
+M(49-(B(Q13M;,$]7/UTV52U&7ELG7V`M/25)2S!2/SHE5E<D1U<V7ETQ*2<\
+M.S5?/RH*35=;43]/*S1@+D5?6%U9+UU60UY;7$]<8%A;-#Q?4"%(-B8S63A6
+M7C9!.2E/15<V7UPO7&`B56`D/#0L*PI-,%%;2%5?6UE*+BY505\^.S567T\O
+M6#9)5"U04E!16T@Y)4E,/54G7EQ7.T5?/C]1*S5`+3977SXS.SE)"DU%7EM?
+M8"$_6RTM.BXY.T(S.SP_7$Q57EQ/7ETS14XK/U-)3U="7U!?7D!46STG74H[
+M.RA/7DLW7BI`(DX*35=62ULU7U)1.UU7,S9$3B]-)#Y:5U]!.UM+/UM--B9?
+M6%XO3S]<3%HZ5U\G42=?3DTP(34Z6%]40B\](PI-+5U53U@_7&`]2BU+0B=;
+M,2=.7C=9-EY<3UY;-5%/7&`W,5];2S4F5U\G42=<3UQ@(E=?3$U@)3=32#9.
+M"DTE5E(K7TI?6U4Y+T([64TI1%9-3EXV34HR+BU)15<Q2E=;34]8.E%?)$PL
+M/#Y%4RTG)DI-63LU4UA#7B<*32A54%]<,BE9(TLD0E%-)TY#5S];53DK,C9;
+M/SY!7UM92S)57UM4+U]@8"I,+39+)D-?(RY*35\D*R=>0PI-7U!@+EDZ.UDZ
+M35U6,DI>6UE*3#!?4")+.DTN-%-9225:+S(Y*%@Y22E&.BU(535-2RM?(E4Z
+M15\G63U1"DTB5#]<+"I--4LV5CM;,RU<5U)-.U9?7&!:*S9&6EI+,%(V.S!+
+M7%,V2DU-2T%+,D537$E:+2=>)CE</E4*34DM4E\J3EI&4BU<4SM%.4H[(5Q7
+M7U!@,S--3UM135=7.D!4(B\\4S\]2U0[6#Q2,4L]-R993UQ@14-?6PI-+3='
+M.SM%2U0K6#HM3D\K2U]+0U]@+E,U33%?0C$N/R0_4%DG2"1-42=9.E\J4S]/
+M)5]!2CE)3U<\1E8F"DTS6T]=5D93.T\K-U]@*EHK-%,[3RL[7SU+5#-204PL
+M1$=%45].-EY;7TQ5,45$1EQ53U<O/UY=-D$E7TH*33A?1E]!2D,L4EX\7UM1
+M/DTN*$,V0$XK/4@X7U="7SU(0SHM1D963$]73UQ@5C9)2$4W4CA=4E]07U`A
+M5@I-1DLG(E,S.TA57EU?6RLU1$,V5E\Z15X[7B5/6$HI3TY+7%=?8")4-DA9
+M*SXG6S!/6TT_3DTQ*SI16S5?"DU51UY,-UXJ0"(R.3]7+$=>6U\]25)?/39(
+M.4L_75,I349&-UDZ.DP\1U)?6C!75S]814HP(BM?-$LT/#X*35L]*5Q/7RLR
+M1E!+/#XR3UM:+4]>8%16+ULQ3D1>-BU/5T4T8%LZ3T8S.T5?3E]052,I.U!-
+M/"U.0S=?-0I-2U\C-$9:6%Q:.UU23UPO5S9*+4E))4XF5S9<1UY+7T%(8$$Z
+M54%>5"M<3U]@(E-?8"@V24LV2EX])TU$"DU$-UDV7T)?5SI&1$XE7E0K3D-$
+M7U4_7&`]2B5+0B=;,2==63]%.UM27U`B6S!@54@Y+TTD/E98.UM+/SX*339&
+M3$U035$G7EQ#.UDV7T%*,4M"5UQ:+UU83U<V7T)?5SHF.BU)2%U,1UPK/U$T
+M8#56.E@V.3]7/#L_3@I-7U`B738S6$4Z,4TI1EQ2)45?/5\^5S5/+5]+15X[
+M7T5&53A?0C=?1"13/ULR7UQ@434\7BA?05(M/"]?"DU@(2A'0C]")UM4+U]@
+M8"I,-3I7+R0_7&!245]8)38G)S8N)U]@*#E46$->*"TV2S0J7TY62DM?)S52
+M(U\*36`E/$=?8#I4+$-*33LW1CE/7D<^5U]<8#$Z5355*C(Q2T)1.4]%-3L]
+M/UQ@3T<]-D%+.E\I+4U?65T[/0I-3U\H.E5$6DT\*RTM.E]714U5-DXF/U%,
+M73)#7V!@6$-(6#U++4U>6TY)3$];34]&7ELT5"I?431675XY"DT_3S]>+E5!
+M4SY<.SQ),5Y=35]<8#`Z729>)C)>.3TW-U,L3UM'7B=?8"=*7%DR+STW22=0
+M4%XV6DY-3#X*35E/*5]=1DM*2%)?/C$N.R0Q7SU2+S!)2#5<33U41$=>3#=>
+M*U\Z2CE+/B\[*4XU3TY?5T9)3%,S/C9/6PI-1UXI/UA/7&`Z2C%&1E9-7EM#
+M-UD_6U4^1#XN)#5/(UDL/UM-/TY5,$9!05Q97U<Z73];33=83TTG1%%?"DU:
+M3UM--$E%3B\N/UU9)U5?3E0Q8%98.49<1B\](RA/4E4F351/1C,[141>75\]
+M7U8Z33!?6D8O7V`G.D$*335/5T175T]//UY253DI)RD\,%\Z)5XY3TY-7R,S
+M.4M"5UQ-/U="7UI1/UQ@5"I)1CM?,BA?15]!.D93.PI-/4I;15]0(C9"7U!?
+M6S5@(SE)*#9!1%92*UU77ETP3E<Z3E5/6#];521</#LP4CM%-UM/759(54U;
+M/SI/"DU;3B];36`A(5]*-5XZ0B\]3D1?6EHU)R$Z4%XR3E4_6U=7-DE,/4I/
+M6U,Z5$)?3R];56`D1U\K.CM?0"T*32T[7T-8.U]+1S0_5CU/+D964S(_4%50
+M7T([43DM.U=#-DY%5T9;.UQ+,RM?8"Y254,V2C%),5E))SM`/`I-(S91/3-$
+M0#U!/E=52SA2*RD\5R0\*EL]3EE&2R%'7#LZ,R<\*TU<6C$U7C<\2E593R\\
+M,S<T1$1542A4"DU$0S\L4RU?)30K+$]7-5]</DM3*D8X,T5<*5T_05XF*B0X
+M7T])-R\U*"\E)D)71%0^5EHV+4U/7V`B5#<*33LV1%0M3RDM)E=%44Q+,4U-
+M.SLW0R\Q/T\U2EH[6$-5(S,Y*%U,4S(P45]<3#8[63U--3)17UQ@+CQ>+`I-
+M6#A#4EQ6)T5=65HZ54L[7CQ/4EL_5S]=6DQ/42,[44M)/%,J4S\]7T\_6RLT
+M.B]82$Q=-D1#-SM9/5%-"DU67C=?8"=/759)+R0K/CE)+5!/15(K34-?0C=?
+M8"=*6SI%,BTR0RHT,UE"-B5*.#Y+4C4H7$LO)3\D/U\*36`B45]71DLA-DY`
+M7#,K7%0O7EQ5."I?*S8V)U]@*#A<,5\D)E4\0%]5/$=?8&`G7V`M(DI"3U=*
+M3S`_+`I-3$=>/UA*52Q#32TY-T8[7V`J7#L_7U0F3CPY35523EM?1T)/7T@U
+M/3-+3"];14E-4S\K.%9:6S];5U)5"DU23$)>-STP7UT\54M`1UP\0U4L+U\C
+M0CHD(S%15EL[1E]"2B8[649*32),0SI'33E%5BQ.64]<8#E+,40*34-#0UXY
+M-EY=7R4W+RT\3$-*0UM1,U9;-3=>74Y>6S=(1U!05E\[*TE-5U\K)U]6.D\N
+M5D-?/5M-5E4]5PI-7S\U/D(_(R$U.EQ.13D_7C8O7&!;+39=(UXH0T91-UPN
+M,5HM*RDG63]>3#=;4E]1-2A45#Y53U="7SU?"DU/-%8V+B]27UTV3U=%7T%*
+M44TW.E=255I96#DW0DXH/RE#7SX_4%4P3BE(/TTC7RQ7-U\Z2S5"7U4L/U(*
+M35]08"TT249?/R]<8#9"*"<N/BU=/E5#,5M+,EA;,EA>4TLI.U,L4DI*34XY
+M-5XW6U4T.#A?0CE9.E(K2@I--D92*RA.6UE%-U]81D-815]0(R=92%]?8"59
+M5R=?2"T]43HK7T@]3U]0(3PU7UQ@,#I415PI15U(6$]3"DU/0CDF4DQ4+D53
+M.S]=5D%--%XH5E=932-47C-935=>3#=?6%DW/3LZ+4TH(T$V7#(^+T5#.3E/
+M(DU.5U\*36`E4T1?4"-,2CM?4$970DHE-38[4#,\+5]0(3Q6.U]81DY8.3H[
+M34A@6#XO6$$Z22).5C]0.THR7U`B5`I-3EM?6$9-,U!96%1-73Q5(EA,62TM
+M3BLJ.35<6U\R+UY@7UQ@434]*%4\4R$I)SM>+TM:.CE-3#P^1TQ3"DTM75LZ
+M2EXS,#<G-C<J,CXK+3Y#5%Y11DU8)C5+0EHW.DY77B4_6$I<43943DM66#97
+M34@Y*4E)+TY+)DT*33LF33I$5DTZ32D]/STW6TPZ6S].2S=,)T!34%\[6C-)
+M+#LM)D]60C-9229?745+4DDQ7DPU+UM)74TS1`I-3$E0-#]962=&+D9?(R\W
+M2U$]55,G)SHK7TTM3CM?459.2$->(U5/*2<G649),RLI7R5>6UY=-THR2DI?
+M"DTK,T]82E8V)$DK2#XU-%-?8"%-,U@Y/$E9.E=0/$TM5$8M,D966S]%7C4_
+M14E+7R%25E9-354B6%<_4$T*34TV3S5*.S->2S1/63,K7E8\.U\N/BHW/U!(
+M53)5-C(R4T](648S64A5.3U++3)7.SI-.UHK/#8N33!2,0I-5R0\/UM..UXF
+M.U]`-3Y3+5].53-..#A92%8U135/6U),32=5.BI>)S`U-RTZ33TV2U%90UDM
+M7#,K7$U/"DU.7T\M-R]*3U,U6D%<-B\C6EH[/C9=53LP6SLV.49>6UU5.2];
+M33XZ2D]3-34J1U(Q63-36E4S5D1..$L*35L_1DLP3457,%,_7"%?7&`P2DE,
+M3U,U/DU%7SU3/UQ@8#]<8%0J2BDN7551/UTD1%]0(D\F55]=(4M'/0I-,TT_
+M*41,1E]/-DY;64I:+S=9)C8C4EU/4E(Z.SM?7&!(+3DW0B8G7V`I8%=>53E;
+M2EQ2/UPU1THX/UQ@"DU!42X\+#Q#-R=27U$M-DTG)E)?/E945E9!-4DE.UXJ
+M2R0^64]8/UQ@0#4X52I'+#TW*U)%+CD_6$I=8%X*328B4BT]-R9513==5E=?
+M2#)54#Q22UQ65SQ74S\^7EI-/5U<+"DM3%166U]'1E=7-5]0(4DZ5U!77#(I
+M4PI-6$]<8$$R+S$I.%E/*U]54E\^-UXF2EY+-S1&1EHW/3='4S,^-D]244M-
+M-SU<5E9+)%127C=?2EDO3RM?"DU@8%5'7BE915]0(B0]3D]7.RM?2E]0(B=?
+M24A-/E4K6#-00T`Y+T,E(SI-62Q&1%<W1C%?*E)?.B4W7U8*339*+$=1/DU9
+M*28_7E);0T=?25HW7U`B*4M!6UY31%4K434M.5%,2E(S-R,J3EM;3U=*5D]>
+M)3I:25]37@I--C]<8%=45U]@+#,V+TTI)D\K)%U@7BA4.RL_.B].1#E/72-.
+M+T\_6TT]32A24%HC64,M338N55<L5UU7"DU9.EA?6$0K3E4C,3]>3RY:7U`C
+M,#I:6EDU1EPM4BI+.S8Z4U9+7U=;2TI*/R(\3#]"+B%%54,U32E5(E$*35Q?
+M-29/.RY85RQ24C%+(E)+7T\_42U<5U<_6U4[14E*-%5+6#M?32)952%+138W
+M/5U(5DI/5T5>6TTW7PI-8"M92RE$7TTR,BY35%8N)B973%A5-E=&5E]<8"LZ
+M.SI/5S8[7T`V5U\]7CI-(S$[+BY35#XS,EM>5596"DT\.RTG+$U02EM&1#E(
+M74Y764].7RU?6B,V/$A1+2]-/EPZ-D9:2$Y*.C\G/3954%,J2SLY)CM9/T9?
+M3E\*35LO7&`C-B,_7BPK2S-?)21&1%<\+$0\+U\K)C->*4I532XV)S,_335"
+M34LG.S%67%95/RM?0C5-2SM*2`I-5R9%5U59/S!042Q-53TS-S,_.5E)+UQ@
+M15<W4E5?6UDW639*/SE&1RTG1BY`7"\Z0%9+.B,[5U1"6S9&"DU7/UQ@/RY;
+M.S<F7BQ)*TI57#$S+29715%3-E]%35]-*UY;7UXI2U5#4%E8-4%4+S$[-E$V
+M.4E&355?558*35<[3DI)6$Y<*"M804U;/%%?*SPP7RI+.TU*6S9>738],T9=
+M1RHL,TM26U8L/#,F-3=8,U$S,S@Y*T,U*PI-+E=!7ETM/#%12U],7RTW5#D[
+M,4LG)R9*7SU+0%TK7&!7-D5:04D]4%,P4C%67%,S,S]71CLZ4DM?4&`A"DU+
+M3T4Y-4LG(E0_.5%=6%M,5U%,/#,U3R=8.$5"1S4U7E8V7UPL1EM?8"<Z1C98
+M-4].2S<P/C524BE**CL*35Q65UM-+3HY13=;36`\2"TU-CM;2S4H55DG7R5<
+M33%-+2<H3U),2S0Q*40^,E\F+E=7/B58.4U/7DM/)0I-7U`C)T4Z3R,]3UQ@
+M)3=4-5PU32]&3B\A+5A4+U,^-E0\0RM?8"LN6U]0(E)+65E(1#(U5EY=-D$Z
+M,5M%"DU.3UQ@.34_7V`D*DH_7RLR33)2-STG2B=?-R]?8"U&2C)#3$T_7U4T
+M*EL_3SH[.U]@+T@M-"\D1UY*5"\*35]+44LU55T]54LF5U\T+4T_7S`V2RM<
+M,4M.33TC5RU.7U`A(SI/*4I?4&`U1TLX/UXG)%,]+38Q228[7@I-6DTZ1U8_
+M.SM*4S]7/5)+-2\S53DU-C(O7EU?6RTV24Y<3E<W7SE>-DXJ22M70THL-CXO
+M649?04M.3T$^"DU/7D0S/UT\5E]0(S!%2T([33U,1$,_5S9+3$]!1$I-/3,J
+M5ED_5SL_7E)555@O6%A&)BM?8"@T0U1*-ST*35D_7&!0*3I3/R1*6T\C755?
+M3S%?7D,E2ETM(R=9/UQ@)5U;7ETU)U$C(R9.05<G5T]/,5]1/UQ@,S$Z70I-
+M4C=01T$Q7BA<)4%>-UA",5]0(S==7ULM-TI914,A43=%-"]?8"(T.B\[7U-?
+M/UQ@3%4^1C)+7ET[6E53"DU1+2DD1U$F0S8O-"TE.3]>/RY:7UTE558D7$=$
+M7B<Y*4U.6U);+SU-5S]<8#,N3C%>*$,Q4CI!2$]%3$L*33]:*ST_/5]7.ELK
+M.R<I2#I++4U<14LU/RQ7/U<]3U,U5S4O02XV+U$G1EQ+.E$^/B==)U4R,CI7
+M040W-`I-)3HF)4]%7%M;2DM?8"Y3-55&1SH[.E=&1$Q,1#LM(ST[.2DE.CE&
+M.UM*2UU;7T`J7U`C/RTX7T,B6CHS"DU1)4@^1TLI.U\Y5D8U22@Y)5Y;7%97
+M6U=>539%)TD\5"]"*39,2T)834LN5U<J5#Q'4SX]3CL]35X[7B4*33U75TI2
+M+4D_4R8K2UY,,58M529,5$951C4U.49>-49633<W/R-67$%5(EI/1D,[/%5/
+M63P[+5\^7T)+40I--UA$33TK6%)/*%E-5E=9/RE?3EL]3UQ@(S997"Q-/$1<
+M*%XJ)DT]-EQ25S9;0S4V5S]035\C3DHO.C)9"DU#,T5#)BE=*RI/4E1;.S4^
+M(CDV0U<K-%=&*S`R,5)*3E5-53X\5R]"*UY53R4W74567%,K.U9:3%1"2U\*
+M36`L329?4"-".E1?0BM833=074@[,5945#]=2#<C*R<C)E);3T\_/CI./DTO
+M3%]<8#HV1%=4.DI/1C9;,`I-3$I+755-5U]@+4)5.%4Y-EU46CPO,UE..C%4
+M)"59/2T\+2<A.DM9/#]<,RU-2S)`53I8040V+C\S5E]7"DU(624Y.E,[7V`D
+M1C)<7C8F6#HF+49>.35?0E]8)3,]*UPN2TE=43TS/SLI)D-&.49#03L\4E%?
+M+4Y?3S4*32P^.%HO/2Y,0T]/+$-7*DTT.T\T244[.R,G)E==648Z2BPE65DK
+M-R)+75=?8"9*.UU%04]++45$.45<4@I-+4A=4E]08"Y;7BD_74I*64XX1U8_
+M*B1,154L3EHN6DU?+%]"0U950EI%*3XM24LW)"1#-E55(UQ74DU."DU?4"-6
+M14HR5EPI4%8_15XS/2Q,0E(M*%1"2TU7.T]8/4]=5DM57R9*5BQ$.S]1*S(B
+M,5E7+UA>3RE/(BT*35TI2DY'32DG+$Q%2DQ4/U<V1%9?43]7/T\[2EP]7EQ5
+M/DX^+TTI05PM7B)55%TV7%HR6T\E3BU65SM-7@I--D\I.%4U.2]&7ETU5%DR
+M63\^+CI-)BPY/EHB45M))4LT32U22THG7V`E4U]@+SE%2D,G338Q.E=+1E=#
+M"DU,73!5.E<Y-C=9/T4U3UQ@/C9*+%-+33A#-4TM0U<_76!&5SM?4"$B2R-<
+M,DQ*5CY`6S5?63TZ5RTZ1#L*35LK(DP_5S];*4%#.4]>7DI1/R1*6STO7"Y?
+M7&`W3S=$54]83$-53"]?(T(U6R4]3"I,5EM91E9?7B\U.@I-1417,%XW)DY;
+M/$TT72TV.49>.4]7/EM9.DXJ2E%;-C<Z4RU=6DXB/U$G-2A36T8M138S.E);
+M/54]05PL"DU-5U92.45>.4\K.U]@+45+1%DX7T9$.U]@(DT]05PM*25?32([
+M/4TU/RD[7V`M14M+4#Q/5T0S1U$W7"8*33%:*2Q45T5?6DT_3RM?)38_6$$Z
+M.UY06T,\2EL]5#]<-U\S,3I425E(7RM?8"TX3U=%7T)++5PR4S%?7`I-*%\]
+M.SDE.4U4/UQ@)5]45#9//T5<*5@L/T(O(3@O7&!$2$0_7S==7ULM-TE44S)>
+M/%8E6%-?/DM3)B-?"DU@*2HM)TY?65]/7&!6.D\U4R9./T\M-B0S.C!/42)>
+M-S1-(U9?4",O2ST_4%]0(S,N3E!!.SM(7"Q'5T4*3397-4]?)SI87U`B*"1'
+M1CI%2#];345?5#Y:7EU?4"$\6ELJ5#=3+2E-55]/-EP_7V`J(S<Y-%XD63A?
+M)`I-/"]+1T%.7%4K454X5T0^5ULJ2S%>.%9;6TI+7%=?0U4Z1#9,+2E83E4B
+M4UQ%2%9-6S5&.U,F7T)>-UM/"DU>4E99/UY>.E9/6R].3TQ;*E503T8M7U)4
+M-3U.5UE*455<*E<R6E1.2BU*0RTM74<Z5T0W4E5?/BLV+#@*35%#3#LR2CQ7
+M*U,D15\Z5DY(.C)415<C(RU<3EDV1#4V5EL_6$I8+U%325P^33HS.UI)."M?
+M240^538U/PI-6$5+5"H_,%E)*S5(-4Y/*S=2/RDU13=;3U);/54Y/4E8+395
+M35@X-D\I*45#5EL]5U)?/5Y;-3Y:)RP\"DT[6#I>*D=",S$[-BY53ULW6SLG
+M(UQ/6U,\3$=?8"=5-S,V1U$J7#,^*DTP7TQ/,CTO04PF7$]3)D9$.U,*32U*
+M7UDZ3DA-32U!04U50T1#0T0Y/UA&-DQ&(3\I7B@C*R4B3U\G0E=7/T5?65A4
+M,4(S*C%`7BM-*UY05@I-/D563$1415$\,SI!)RDM/#(_+%1#.S9+3"5,-4--
+M7RE#-UE%.ULP3UY=-RM?)#)81$%.32M&1$-645!7"DTB3%=3-#XL33Y.32M.
+M*SXZ4D164BI66U]@)C9?5CI87#4K140]1B,O,DLJ.3M"535/749-*2A86%A=
+M4S\*32TW*5XC2U-;/4A-2SPJ4E\L3U,K7TTU3SI%/RM)74]95%)-63]>4DY:
+M2D,K5SXX6C]-/$PR0SHR-EI15PI-)T9?6E-;4S4^5$5%7$=25C=9-5Y:53A:
+M/BA+/4]&04Q923]=5BU*1$)<,"I55$U/74-.+RX[7B4U2CA&"DU<2C1>-SA:
+M(C8M.%].5457.DLM+2<F5U);2E4I)3M2/T9+03\F3DHS.CL[44TA+%0\5$,^
+M-TXW6U977BH*34I%*T5#4R<M7#%33U$I.UY4+2%.)C5/+#I63B]8/UM/5S9+
+M44T]53Y",C=71S8]63E2+5U9.EQ?5ELY)@I--U\Z2BPO-%U))%!1)#XV,2Q2
+M5E]&-E]$7CE.7$]=64XY/T\[2EI3-#@[)EA50U$D54DF4E9</"I-)UE#"DT[
+M1C=;3EI.+35&7%HO7SE+22LB6#E(73Y>.UE)*#5?72(V35-'+E157E5!-E-#
+M7E,J5T5?5C,[35Q3.TT*35=72E!?)"P^6S,_(TL[/UXW-4]?8"0E.E=-,497
+M.C<[2E=?*S,Q.#]&7U<]53A.33L_6RE4+3]>1S5/7PI-8"8V3R=+7U$Y)TPX
+M/UXG)$E1*TU45B5%7T\M,T8U3RLW.TY67SY*.S,U5SH[)E,_7$Q6.U]0(2%)
+M3%,L"DU,/T5*4RTW1R]80THJ/%]?8")67CM9.EI+6#H_7$$R.3]%.3\I.U]6
+M-DXS3C]&7%%)+5,_+5\E-S<_(T,*33Q623,[-E9?3E9?745+3U`_7#(I4UA+
+M7B,H729&-3\K7EI/5T5++UPR2D]=0%<W4E]08#0_7TA1.D\S,0I-4CXW7V`J
+M64]//U<Z4U\D2DQ/05Y:.4E&.SI4/UPO7&!45#9//D5<*5@W5D-0-B-?8"DJ
+M+2=?55\_7E,U"DU:3C4[+U=*7$A-34]?(E$H7U`C2UY?7&`Y2ETX5DM?/DM'
+M0CI2(UA$+4U/+2$[7TE#/#]>0%55145*)4(*34Y&5DY:7DTU7SQ17SY&-UY;
+M-#Y+/B98/R0G5BXZ6S5%1$TU.3A60T1?3S\^7T%+1U5>)%]!7U,I+50]4`I-
+M5E=>73P_7&!6+3U43SY"/$]//%%44SI123I?/E4R43=?35@W7U`B*DI.5T(K
+M,%5>74LF1U]0(T`U)S8G"DU?4&`J2UPM2RDF3U8V,UE/3SM+04H]*5PL/R,L
+M/#]<8%%82UU;7T=-*5]<8"4T3C0C-#M1,5@_-U]&+3H*33]?6"0K-"Q'0EM0
+M6D]?+#I,/UQ@3UDU-4]!2U@Y-39?6$5,/U<_7&!'22E?7#4S2%]!5U@[5E%,
+M5D566PI-/RM?)4X[7U8Z1F`R/BTO(RI?/E5*55]</DI'2$Y+-BY,/BY)*4TN
+M3DA;0BT[+C,\4DI74E\M)U]@+R15"DT^0EPA7B<U0U]0(C`W.E\^.U]%1S53
+M,T\C7D9:-RTM*3@V7"U-*%=26TA853=9/T5>-D`C15]"0DI?5B\*32<H5UPM
+M42=?8"Y`33<J7BLK4ULU+4XZ3E!</59:4S!044TL4SLY13M915]72E@N7U\_
+M+%1'7"Y:3RI15`I-3SY"/DM`.%Y;*%H[6#Q>*4A=+4XE5%9;1%915R,[2#8[
+M7EU?/4M6(C(Q.T(O6U4_+$U7)U]08#)=/UM4"DT\4S%56DE<+UTP3BTT5#TX
+M7TU$0S<T+UPL0SM-4E0T6D=6,S(T/TTG/S%:,SI*4RTG)DP^6UY:4RLT5#4*
+M34<[5EL_3CA5-4]82DM05R5=359-(DLM7T\U+2DG/3(K7SU?5SI7*44T15\L
+M-BM&6#4X/T8Z3RE?)C=60@I-6E5",U,O14%!-35#-U]1649?4U]@)SI=,D%4
+M735?/C,M64,K75=?.4M"/R551UE$15165UY:5U]@8"=;"DU-.#A+1EU&)2=8
+M0U)&/C-26U%*659;03HF6CL[7TI6.DY"53(M3RPY1EXV,B]9-C9,3BHZ540Y
+M2%E=4DT*35\^2R<B52XT5%$U+T9".2I-7EPU2E<M+CM=5THW7E4C7U`B4RLU
+M-EE453A9)41`1#8Y-E].52TX.TA9*PI-63U/7V`B6TTW/R8W+#X]*BQ(1S\W
+M6SL]*U1=3UY(+R9/7T-3-3%5,UE-)UPO6T]82SXS7V`M14I12B,["DU-*51/
+M7TPE2U]0(D$M-2\T.45?3#]07E,M7U)11#=?5"9/*5$W7#(Q6E8G7V`H/#%.
+M1%]<0%<Z3$9775D*33,Y75,[-E<_43\M-"Y&4CXW.5LU7CE%1$HR64-<3UE&
+M1#E%2U-'7#%5)299-49!.4I:4U@\+$I:52Q*2PI-3E\\35U77T`M/#\]4RQ+
+M/ST[.E=>53<W/R,Q359.,SL_3EXR5U],33U.(U]@*$0S)R4_4#DG221,1UM/
+M"DU?-5(_/C=>)DQ/434I3EPM5T)>-RA.6U)?4"(G7TE(33LT3UY+7U@E-C-8
+M2U]0(C$[3R]>5C]<8%HL-D\*34@I/R(^(R=80U`N4E]0(C)!,%]=/T=?3%4^
+M33\K-T1-05]2-"HG/5]37C]>4S5:54XW7EQ-.%$M*2=(5`I-+UY)0U]0(59&
+M2U]+22]<8$`M,5`Q4CXS)UY>.U]/1D$X-EQ:,UU=*5\C-S0X(TDM5T<Q7UPB
+M24Q?4"-*"DU?4"-`,E16,E=?/S%?3RDO6$]82D-'355<3UM41U=%7T)(8$9$
+M7EI77ELT/#]>2T->.UXG7ETS.2L^+SH*35=73TY?4"%:2#A;/B4X.%]%-UM/
+M75I`(50[*DP_5T]81E]"2R5//3Q,6RXR.STI3CL]754_1DI/2S5=(PI-.%8L
+M0E!12UE#+%977T]*3RQ=-$])*49$0UX[.TY>.DPZ53M6.4\Q2%Y4+3,W/3XW
+M-4M+.R==53]?)SI0"DU9+UQ@-%(M-296.4\N.48[75<F2RTT1%U02E8^6UY<
+M53XQ-D4J22A=.D,F+2XI,3E)*2A;0SU=5U--53L*35=`0S4I+2M5038U1E9;
+M2E,[*B5)+2U$5S\^1#E&7UM92E5)74I75D(N,UXE-EXZ05)9,5%=6BE=)C8T
+M0@I-5S0J4S%?42LU435+1C\A7$1'5B];+RE=6B]9.EI+.S<_,T53,5E#0DHQ
+M63D\22,I759/*#\E1T(J.B]1"DTH3EM9.R=92#8W7EU<5U=/7EY*74!$5DLG
+M7%4^*EXL+2]$3D5#-B4]4S9:35E'7S\S-45"*%)$.E0^-%D*33\W6#\K3R8Y
+M/T8U15Y=7UHC-R-7/EDY3U\G.ETX54LS6S9<7"I15T5)+5Q4/E5-3UY37SU?
+M4")@55DS/`I-7RT\,C\K7R4T,4E>4T0Z,D53,2<V55123$Y6,44W7ELW,SH\
+M3$5=3C8N+3DF1$-635\B53Q/)R-.1E91"DU56C58+TQ504TY*2\K5SLX74]<
+M8$\U2R8O+2A363\]*S9)-DQ4/D96*E(Q3$]$5BLQ35],34XZ3$Y=-U,*32TG
+M5"4V7T%#1$M5(B(F5TXK-U)65DU<3E5(5C<]-V!>+RTI1%1',UTO1$U%.3LV
+M+C%(7UY./EM>)DQ<-PI-03D_)R1+(U5(45U2,4):/3P_.5LV,3]=23I$3C97
+M,4Q3/SY>-UM5.5U-3D-.53=>)C)+1$=3,4I/75M9"DU*7#8T/SXO,2H\0EXY
+M35X[6TT]25PN-35-/$XE.U]'0E=?3$T\-R].0U]!7CL]34M,3T%.4RU+-5%-
+M5EL*34\I.UM77V`Z6SPI7R1"/$XJ7T!2+S(I6#A?*UY;7R5?/DLG7#8Q45%>
+M)EM13T9>-4A?6$]>1D)52S--+0I-63]7.%]/-4->+"DI)5PK5T,R/C9*7TY=
+M4SM/7D9"55M43T$O(T-<,5@W24];4U%=(E==6EQ?7&`Y2ETV"DU635]-/UU=
+M-5@_/E<C*TI45%0D0RI71BQ24BI5+U]-6S4O7&!(*C='7S]92E`M1R1>33@O
+M73,G7%4M-SL*35E41U,_5S]82D-`35A?3%I?*E=?8"=5,$PL+T<R/TY57T%+
+M2RXX*49#7%(O/%)?43]1-2<O+2-9*S]/)0I-7EQ-,BTG(DQ$+U)+,SE5-U(V
+M7C=?4&`]2&`[*3PP6S9>.UXG7ELU/3M*+U(U7U5'5S];33<F7T)?4"-""DTZ
+M2DTI62U&2RU?/39`(T9;1E-?33)8.3]71#I04BQ+/RM=6DX]7#4^)SE/3%I-
+M(3I+335/1EXZ3S%(2#4*33=9/UA&7CE/75I(6TY10T](.TI5.SU-1$-67RI7
+M4RLR2C0X52A'-S%%6RQ>/UE/72<U2%DF7C=>*DH\4@I-33PK3E]!-UM/4S4Z
+M52LR6T(G-4M"4UX[4R5$.35*)3A63%A60TXX62A;03]8-C<[3DM2*31>-%TK
+M5D<L"DTZ5T%:549.*%9$.SM9.S].6%];53TI25)67#Y),C8\4DM-1#=9-EY;
+M7%==5DXW5")<3RLU1D--6S]33RP*35Y:2DU<4DU?/C9.4E1',SE+044V64-6
+M4C,L3E8M/UM/75I(0R=&1#$K5S--+R,E42A-45D^64U715\M7PI-6B,W-CX]
+M+5]35E]</4M%72)43CI3.U,F5RM<5E4_7&`Y-DY&7CM>7%=?/S4^3S%#6T]/
+M)R4T15M73D,V"DTU1E9>.%,M7%=<-3Q32B5/)SI:4%171%XY(SPK33DU7T%+
+M2B8W/$]=6DXG3SM26DT_)",V7%0K,BI26TT*34I?+5Q-)3HW*R0J+DQ#4T]1
+M-2D[6C\I/#9-0%,^-RQ6644W7B=?.4M".$LN.4DM4S]72EU03STG5%\T*0I-
+M+5,V3$)-7SU653=-+V!<+$=;4U)65RQ74DI26SU-/"=,6D1)/R(]/RTG6S%0
+M3T!34%-?.C9-.TE8750L"DT_4SLV2U8Z4E1&)E=#0TU86#A86%]0(S8L2DM=
+M6U\O4E1;,CTC35TM7C8N+35+.D,W64E/74=9.ETF0S,*35DH1RDT2CQ43STJ
+M/#U+7%,M7R4U+S4M*4-5,E%&3%M&+UU3+$Y764]8/UM+-4TM)UQ-,5,_6D]9
+M1E]"2@I-5CA'0"E,5DQ=,25+*5Q,5"Q41#L_7"$V1RY/4RM-7CLZ4E54-T)3
+M,C9,/R545%)?6DX[4RM!-E]715]>"DTJ5S53/$0_6U$_1EXZ3T4\,2-$2BXI
+M6T4N-R\G1B@Y-5XU1CM>*4I:.U@[,S%+2T553CL[7ED[7V`M14L*348Y+5,_
+M*S<R/R,K63T\3B567UQ@*R5*5U`_45$L7#5?(40^1C,W)"]$7UI37BI(6TXN
+M4T]+-C)73C5#1@I-040W64A9+TY--%1?7DM>6U\D32XY/T4[635+5T-04T95
+M7"9>)%DH.%8[,CLG6T]=5DQ&65PB7B1?32Q'"DU?)$M-/D]<8%-4.U]@+BI+
+M32@U-U(X7U<Z7SU?3E5'5RP\/U9&,UU63S=>6S-"*%=(.4@_*3==6$TM-E@*
+M34%>5#(_+%=7-5Y:33(P.S\I-SI*7U`C/RTN-4]=*$1?5S5*4C(F,E@W4R@]
+M2E(M7U`G7SE*+CXY3RD[.PI-(RDU0C-?-C!?/EY=7U`B4E4G/#]:14I@)DTI
+M+5Q76T=?3TE*4%E/33592U9$5BL[.%L^+B8R6D9635U;"DT[7$I+7U`A72U?
+M6%LW,"TV+U@^5S<V2SI)/S]044TG.5%?7&!@-5<M7U`B+RU?6%LP+B]1)E4E
+M(R9,1E4*34I$7$TU3D4Y/T57*S%<4DQ5+C==62TB5UY=-D%(."U?6#U47S0O
+M+$1&354V6$,[.DQ"7SY+0ST]7B9=70I-2R)?.BA92%9.*%]%7EU=63I=,#HO
+M7S<K7TTT,5].6D(U+BQ"2%4I,2XE5"-0)DY$5CY9.TM26UE/7C9'"DU%6TU-
+M/2%)+T%6)S-93RY$1D1&1E]/+5Q-.S97/4Y>.D9;34]<-3-;+BE)6S(T0CDU
+M-SM-2%XW/30Z4C\*35LU+E<U2#E$1U<Z2TTB3T8^.4TW/5\^5E4I,4U)8"TV
+M+35?04E3.SI&0T,Z,UDU-SQ5+D1,.44W/5Q+/PI-6TTK1F!#0$-<4C-9-5<U
+M*2<G34-?4"%)1E<U2#$Q2R,[2DI?/595-4-?/BM90S]1/RTT7%`D/T5?4&`O
+M"DU=5DI<6E\K-4E/3E];53!2,5\K-4`\,UPQ*R8M32E-6UL[7EQ#.U)=5UY2
+M53XZ4BM=5UXG/3=,5T(^5T,*34]/(SU=.DU=6U(]4E]0(EQ/4S583RTI348Y
+M-3M9.EPC+%H_*S190U1<(RM&0U)%)C\\3UY=-S,_(SD_7@I-*5Q26T8U3RE$
+M7CI.-4Y9)D1&.U]@+#Y+24]!4E=%7BDM3T5;3U<L13%!/UA$,S\E/U`Y)THD
+M7#L^-U]+"DU&7ETS1D-$5DU>7CM;3UQ@/4E,7$,^-UQ4*S\^25,S/RI76T8S
+M6T]=5D\\+R$H6$A93RE#7S\_4%]0(58*34Q>63987E12/T]%6E]0(59-(BHY
+M/RE#7C8S6T]=5DQ>6$9#7E0R/SY:7U<Z/#$F62$\.T@U5E!2/SY(-@I-,CE+
+M0C,[.U]@+"I?/CI)2#U714M<3UQ@1STP3U\G42E?3DLU1$0L0S<G1C!?*R9/
+M4S]73UU&1E-+/"T["DU2/T\H55]8(S141UY,05]73U\Y.BDU7U0^,UE*8"@Y
+M)40U-CE+0UM*6T].541>8$A60UPJ5EQ41EM3*T8*33(R6#L]7R5..4U/7V`H
+M6S5#42\^/SA?(TY(,S%-7EQ<145#-U\Z.UDU2S)45E-?3%\S.S93-U]%5T$V
+M+PI-7T]%.BA%42M&5D902TU.34]<*RI5,C%?5SHO3E<D*S];-E9-(D%*6T]7
+M0E]1.TI#.UM-+C8Q-DY)*%]8"DU-2DTT0RM.2&!#-3U*5%5%5S]1/RTU0U):
+M+RLY/U@Z,3$U-39?3EM*/$P[.TI-*DTR,5%?+$TG+"$U-BT*34]=5DE%-U]`
+M-38O*THQ.#U/5SI'1F!!0#5%1E]72E0E-4I+7C=..3I..T4U.E-$(2,N5E4V
+M7UQ@,#I*30I-7$I*32565T8M7$]<,RLT*S(^.2A65U\]2DA1*2<M7SXZ2#DO
+M+$9>6DY5.DTK)U\D5U,T+%=25%)#0U\E"DU%65\Y3T%>7$1?63HL3T5?75TU
+M6%,M*$LP32A26TU/7V`G53=*-U$Q.C(S,#8Z)4]=,RA++3=%3"9%5R4*35TH
+M3"PZ4BQ3*TY?3E59-C@Q12I))%L\)RHL.%(L5BE)*2E)1D0Z3DY>)RL_7#4X
+M5EM?1T,I-C\K6#]5*@I-+E5&1#935E%02TXY35=3-3Y>)SI?4",E-"U94EM/
+M5RQ%.#@]/T\H4BU:5$A22%(O-4E/73==6UXJ229>"DU:53596#A?*5]0(39/
+M5TI$.E=#5DU<33Y,/BXD-EM2.%]0(SU++DXF.ULP7U5*6R,C)UDL/U,R/SU?
+M3S4*3497+"I?.CDO5T1'-UY=-2(J/T!?-"M#4CDK+CI8-UD]3T5++2E<5U]@
+M8"<]7%=?8#I:0BU))DY)*%]-)`I-+ELZ3U)+75I/*CTG33TX52LU*TL[(DLJ
+M5UM364U.5BXO7B<]7SU7/UXO-4TW+$0N1DI*5SQ562DH2S\]"DU-6R5%-SU?
+M/5Q2,UDZ65HM,C0_/5D_/2U:52];-RP]72DC*%1$+EHY3T\_7E)45CY70UY3
+M4CM&.4]=64H*35@F5%P\,C%7,E!-.U<T1E5&5DM94DQ4/UY25EL[34LR3C\G
+M54\C(RQ-4TD]52<H3UU9/4T]-RP\5RHZ-@I-3E!04SI%2$Y'6S5?5$)<-C%/
+M1E]9.5%?3R9?72%+3"U,.RM.-UDZ7#E57U%(5$="*S$],DU(.2A8.#U2"DU-
+M)RE=64U75TU/7&!0)D\V*"A9)D%7,5([15]62DHO3R517B1.*DLT6C8O.R=.
+M5E\]5DTT.E(L3UY,-DL*330[*RTG7TQ5,4@Y7$);/31<4UY=)TU?72)*729*
+M54=2+5])-#]=6B]9/UQ@3T5/14I=)E)+,T0R-BU-,@I-+29.5D@U1#9)+5)?
+M435@(S\K7CI'*DTM.C,Z33!?.TA=5E=>*DI@33L_7&`Z2C$Z25\Z14A>52I-
+M,U)`"DTW+TY5)RTL3E9*/$5<3UQ@334W1$Y&.UXE2BXT(C);1EXY/4]<-20W
+M1BQ.5BXQ/T5?3S4W0D17,RLH5U<*34U/7&!74S53.R=.7EM-.U]@)CI,(C-3
+M22A?7&`V,5]<(C9(.2M"+SM<2S4X-UDU7ETU/EM#-C$V5DLR8`I-6$=11TI3
+M.C=(3%9(*DI,5T<K335*7%%$7B@F2C(K7EE62U].4557)U$K1%9>)U\I7CL]
+M*3PK-T4]34E="DU5/#L_*4TY*2]=53I8/#4T32M%0R1:2BTJ*T-3,BTB.U$Q
+M2S542S%-/3\J5UPN6U]@+BE+2$]!6RD\,SX*32E(/51"7UI.,UXG7V`F.E$[
+M,U!/2E126T],3E4_74I.5EPI7B4],%17-4L^7D=$0S8N+SU.-3U3.T]%2PI-
+M)D=;-C1/6TD],U8D.%Y:/4TK)RQ,+U=(7T\_5SH\5T<[-EXV+U]/2DDI62A?
+M*U]"7EU?/4@Z,B<Z5UM2"DU.5%@R3R145T117TY57EM?8"=*4RM&,C-;,RE>
+M63M+7V`G2E58.2A604-?/BM=5UY=-CL\3$]61C-=6$\*339&,CD\/E8G4$,Z
+M7C)+.TTQ52(Q229<4SM?4E1<33]>7C]%7UM92CLW*T,W3RQ<4E!4)3TY5%99
+M0597)`I-/T<^-UY<-48U3T5?6"-?(S9$5D96/D%>)UTK3%TT3RL_4R@Y*4U+
+M/U=&.4]8/UQ@3TI*+%=",S)8.4DF"DU,33!/22U74SXW.5%>.$Y;7BM;4ELU
+M25%#7EM#)#TR-UM6-$A'+U8U63A?.D@[)E]<)5I%23)7)$)17CP*35,R+$I-
+M3EXW64]8/UY@55<W14L[5B]%45M&-3567RU=6DQ6(U$I/BTI)DI+/#,Q7UQ-
+M)49--35-3UY<2PI-*U]:(S-+1S<K/U)9*%U,1T8_5S]82CPX7$E'*C1#)5XH
+M-#PL5D=:.DLP2E1#-U0Z2E963E1`3B9?/%%+"DU<2RU=639+4U]08#8W4SI!
+M+4A/1"Q/1$L^*E1#*TY+3DTE-3DF.CE/6#]%-D<G6$,R+U!&3$]22UQ74RT*
+M33-/3E\L3U)--"LW,E%,4E%+-3XV6$9$.SI+.S9+-$@[63LW-%1%45Q714Q.
+M53]&5E\M7R4T.EX].DM-0PI-1%Q1-5]0(CY'5TI/.C<F2E8Z4$Q24C,Q7RLH
+M4E]0(5I+)U8U-CI.-C1)/%%/4E$L7BLT)CE/*S8F,U,J"DTI+3)$-3]7/4\M
+M*%==5SLU2C(G4CE&7U<Z.TU+2$-<+T\F-$)#74Y.2T(S.RDB54Y/7E5?)352
+M+E4V,B\*35\V.S]=53I!1T-<4C(O7&!!1%]6/UM-)#Q3,%(^-RDU1$51.RT]
+M5UY,.4H])D]3.S5)4E\]7C9")U\V+0I-3DI@*#DH5D1$.44W.DTM7$4]53HB
+M44I*52<G)UQ5)RPA-S8O.E9:2S);.25>.UM5,BTF2RLU/U)6-U\Z"DTV0"(S
+M4EU-,%(M34-65%5&1%]!1%]0(EY*25<L4S\M,EDP+C]5*B8F7$\E-CD]4E%?
+M+5Q5+4!-.#4Y34P*33]<-3%2-R,M/C(L3#LM3E];64HR*4M!1D1?53I?+5]/
+M-2Q"(E$D2DLB3E4U-UM-.E5--BLG)RE=1S<]+`I-5UPO5S]%2S1-*#A?6U,_
+M/E]/-2@D55-?8"I/7V`H)3,Y+UA/14E,7#];3U]@)41'5T];52U$0SM934Q3
+M"DTT0"A854,Z)B]=5D]7/UM-.4U1)R-=2#]=5D\W7ELV029,5T117T]/6#]8
+M14LK3C5&7E12/T\H7D]=5D8*36!76$A71$9'7B=+-EY#0S8Y.%M!2%8U135/
+M3RM?33];33PW6#Q5,S1-,DTU3BLJ44TU1$17.SA67EQ22PI-35<M3E<J3U]@
+M(E0W7U!@/DM.+R1-62,]/B0[0E5..STV0SLZ2DI3.T]&2T%=*4PU04%#-E)&
+M1$U-3%=&"DTK63L[/4]7/5=?3$]27U$[.E1)52@\3T\Q*UDS/D147U$I/3!?
+M4",H*TQ8-5<_7&`K4%991E];641*2R4*33E>*48U2%A=*U0N6%Y<5T517RM?
+M8"Y3-2DC)TM4+"L^-THT4RU?(E1'15(L3UY;3C9,1S%;-S0M*U536PI-/2$]
+M+2TI3CL](RDG7$L[3UU61RM6/RLJ,30Z/T\D/D@Q6D$Z6D$Z32])/5$Z*UU(
+M0S5&04XE.B<[3EX["DU9/4L[/UY>2ETH5$U&-B5%7U`A2D\O7&!10TI%73\V
+M74<I+4D]3R94*RQ3+29+*U]@(3]06TI;4S(Y148*34U8624[/$LK,DE!*B\J
+M.S)12TU>.D@X)EQ2.48W7B9!24U*52<Y+%)04C]735=72E0I+2-2559?6S)?
+M40I-/U$U)UE%63M?/597(E]<8#\K-3@U13DU.D),63%90DA2*BQ&2#%;1CH]
+M-D%?4").0E0K/C9+/UQ-34Y;"DU;2RU<33<M2DQ.54I)4"I+,4Q.53U.6456
+M530R.20\3$=>1"I23$LK35]/-#(Q7CE?3RTU/C)5.BY8.4@*325>-4]=1D<L
+M5%0[/STU2E0I+3(A34P^54H]755-4E5&43=$0E\K*2)27UQ@8$I4*"M"+CD_
+M14LN-#)"4`I-3$4R*UU:2EI;.CU.5E1`*S9:1EM%7T)*2E542E]08#4T64TT
+M0T->-B]91E].2S%20#I#/C<W+%I52C0_"DU'*SM0+$]&+5]0(BY45E]/*THZ
+M+UU=*5DL/UQ@(DI?/CI,7S0[+3L^+E5#5SXV4RQ3,CLV25Q/*R1&-$(*32TV
+M5E,_/E]!2C(N6$,U225<4SU<5U<Z24LC(UE74RM.7C=>*UE*3C`[-3HS.DI+
+M7%4X1"1$+%9:5S=26`I--CM;3UY3-DT[+2U?/4->75\^2RM#.SL[2TX[.UU6
+M32I63BA=4E]/)5Y=,"$I+RDM63]<8$I?6$];52U$"DU$.SI66B5*/2\I)E0_
+M4E(K75I",UM+.S8V0$A*43,K62P^6BXQ/T5?3E5&-RU1(UU)1%Y=55].339/
+M(U\*36`J1B]?/4LM3C8[6S,I73I@*B]")SLI.%!16T]>-D,M-B<[-CLM)TXV
+M,SI-/34]6C,\,S9<*E<K*D]%30I-3E\]52)-2SI62E\]7EM?/DHN-C%/4S%"
+M.%P]2SXM7$U/+2A253I-+2U&35DO+D1.+T9?5SH[2B]"*E%4"DU>+STM+2<F
+M3UY=-R):45PS2ST]5#U!)E13/U=/3DLQ4EQ/428_32DR7B1;4S1%35M"549!
+M1CL\5$0[*DL*33(_4%5%7F`].CI:5$=;+RA27CD[4BTF53XM6D583%4[-$8Y
+M.2M!2%A?4");)UQ-.U=0654S4CTR1DT]5@I-5U,E-C$]5UU9/UQ@74I*25)#
+M4R%24EY4/E\J4DTG-%5$5U,M3E]"2R5,2U$Y2%U75S9?0C=>6S5:5D9<"DU.
+M,UE/3EM/1DM+*2U*+C-9/4LU+34U34Y7/$TR*E=256`D0S(^-R93/U<U2BY6
+M-SL[2E,_434S3CM#4R8*35Q%.DP_4%LZ/RHA*4XZ,SU-7ELT+T5?3T4U3U]@
+M(59',4Q23%)*3U]@8"I*7%Q25ELK35XY3UM.6UE&2`I-8$962E,^-E)?+5\^
+M7T\T.S\K1%<^-E9:3%XR.EI$-U]@*3E+7U`B+RTT1#Y(-U0_4DQ,+$=>55Q2
+M53<J"DTA/E<W)$=7-59?6U4G1%<C/SY$2D]?.C5135]8735?6UX_7&!6+20^
+M2#<D5EM=4R<G7T\R.T]<8%8V25D*33E@,BT[2D]71E<U-4=&04U55RU--D$Y
+M*T(Z.C$Y*24U-5]964LU/BXF+UE)+UA&7T(Y354T*$I*3%1550I-74I$-SI,
+M/U,J2S]>034S53)>7%I<0U\]3%9763]%2SPW63A6.4]<8$5'7%=3-5,K/SY#
+M7V`H/51&6DQ:"DU`)$)2-R,S*UY;-B]9/UA&5S4M(R<H4S(^.2Q75T]8/T5*
+M3UU(0S8[64DE-UDZ1$!.+5,R/C<C*%=3/U<*33U-.$1%2B8F)ULN2SL]5U<_
+M3S\M-E4[/UM*7SY+(T$Z.4L]/UTW)UU77SI+-DU(5E<_6EE*8"LU4DI>-PI-
+M7%0Z7SY?3E4G*2,C33M=5DTD6E(^,UQ,5UPW7#)45C-334L_+295-B$C)2,G
+M62P^5SQ/4E]0(5I,6EE!"DU?4"$I1%\_,CT_6U4Z+#1$4E!17TY57EM?(E4Y
+M15,I+5U)1%]4/4Y/75DZ8"Q;43%<,RTC5%91.3@[2BX*35A.)EY;)R)66D\I
+M/39>*%9*-U99."U-3R977V!&2T1;5U\V*U].53=+15U)*C%57E%#)UA".EM/
+M24M2.0I-22E91D0[7SI*3E,T.S]&2#DZ,%!>7%4V*R)<1E167D%/*"9?/C%-
+M3DI/-DE()D9?5DU80$P]+U9?*SPS"DTK)E977EM.7CI&*C%;04A=55U(35@Y
+M22A635DI*%5$7$U&7T$Z3D!#1EXW64]8.D-50E=!-E5#7$TU0T0*34%>-B]?
+M/5].53--22DM2E%>,E9;7SDZ3S%"/%%4544^*E)>-RA77ELU2C(S75=--UM5
+M/%LG.U<D1U,K7@I-6U<_5TI/,3!7(S%?+3TL5UY232<J*T8K-#PZ44U91$,_
+M+3-/34),5E8O/2A77E,T/#U+)E,_3RU41UY4"DU3.TI&-34Y/T5((T8F3%14
+M0DQ.6U)?7&`]2BXN5UQ.24U7755*+U@J(4,Y)59+-"]%335#6U4F5UY;,"$*
+M32<G7$TS.S566THZ2DU?/4E,6SLX7RM?0DA@035-34\M*31&5UDU-39>6DTQ
+M4U\J3"P_4SM&.DDI)E9?6PI-56`E/CU&7E)3,4M=64A6-U]08#U+15DH53E(
+M7RQ$1$1>.UXI2EI"/UM+.SI9)4E)1EQ'4E@_6$]/+%4X"DU5(C)9)DI14RM9
+M/#I+7$TV*#9>.C,]7SY++S=31D8W/2U-7SU?4",K+3I&0$%#7%(S/5Y<.SU?
+M)%9;7B8*34Q0*T0]52==1S=935=7/TY?+391.C,M3E]0(B\]34Y:3#XE34U*
+M3E4]3U<Z5BLR,3U75T5*4"E)645<1PI-64A9)5X[7ELQ*3PQ7RU<5E=9/UA*
+M/2Y76T=?0#4D1UPU8"4^)CHM2#A=3$)?/5]/-4<W+$=>1%(_3R5:"DU52%%*
+M2S!16TA57ELV,STP4%\Z22]7*E\^7TY48#XG,%H].E<E2RDI*D];43962UQ7
+M7E,W+C,P7C,I*$X*35L]34M)2"U/5B987CM?/5];64M'6T\]6513+$]3.TI<
+M1D$J-RQ35BI4.%Q,,D=<4S0[139'*R93+$Y92@I-/%0J44U<5U)55$<L,%LV
+M.D\Q+T$E24I3/BHI*U(Q/5)614Y40DU?/3E/15]>*DM374E%7T$V3S-/03L]
+M"DTK(TE.23XW7$Q41$)*4SM/6#U-.E0_0BQ:05HY2BU1651245]02S5*5DU?
+M*T,U22]/-3--2S=915<U2#L*339$.35>6DT]65-$1#Y:,UM--DA?13<[35<_
+M/DE,,EXW754Z1#=;4SM/3EM&2"0G4DI66DDM4E17.E,T5PI-4E8[7B9`(59;
+M/5U61RQ/4E4G,T]=1D`A4DLQ7T=92B]=5D<K7U`B+E4H8%,[.%I+2D]&,CXX
+M5U<Z1CD]"DU77V`Z0T=$7$XF2F!)4C(M+4HB6S5>-BTZ3BQ/4E]052,[,%L_
+M3S\^.DLC/U$T8#19159+7$I?*S<I5UX*35973R8U1E9*3%4]/2Q663U-/%]+
+M349.6E=.34I-33LZ53,K6#`L53M*+4E%7$TV1%XW7B9)2$DK049$5@I-2DP_
+M4S]1/RTT*DQ71RM?3E0Q5R0\/%1"32E<5EM;2E];33@D14HM/UTE-U-)1CM9
+M/TY?/4L^3"@X/UQ@"DU'1D]72EE&+EA$.E-?/5!?4%\^.UDZ5TTM7E0L5EM3
+M)5Y=754U2D(C1EE?0C%8-5Q:.E5&1$XK/C(N,SH*359:+4]8-E]6.E4H3R8W
+M049:-4\I55HE/UY.1$977U0J3CM5(S$_(SU56$)$.EE43B=2-D163%=3,4I3
+M/PI-6U)?6S4Q2U@A7"=:35E#-B0]629*34U<4B8[/$]?0U4S4S(M+C1;.E15
+M(E5$33A6054R5T8W.U]@8$Q+"DTU1C<U74):1"=60B=;5UU63RTM.UXG+U8N
+M,4];+$\I1C=>6RQ/7TQ2549)6"=54$@C74I#7U`B7D9)7%0*340O14(U6"DI
+M+RM&-3A57CM;3U,[.E-;5U]#7DXE7U9*3S9&5STH2S]07UQ@42TY35I77R11
+M1EX[/5Q5/@I-,S,_3R=,,D-;235.-EY34S(P45]035U9.DA,4SM/1DI;/TU?
+M25Q?7ETU8%TZ6R@[)R<T7B@]-D]()DU7"DU&05=3,S9"6U$H3UM2+SM=63I1
+M2E5?(T]?*#9#5SE?53Q'7T@K-"U<0EE".%%923Q)6DE(7U!@-%TI1C4*335.
+M7RQ-24].6S8W/$]?/S4],R%85E0F-#XX1DDV7E<[7U!@725+65!.44U.22]<
+M8#Y**UE*6CA54R(U+PI-5T-:3DQ/)"9%6D!/1C`W54Y27UT])4I4354F2RQ+
+M+5\E-U(Y(SPS,#(J5"Q#,4Q/7#)-34M*/2M>*2=""DTK,E8U/TTW5D8G7BHN
+M6C=3)3I-.%5(7SHI63DG5$DG*$L_/3DV2C-3*EP[5$]83UH^4RM=3U16,E9$
+M7UD*35HV6SM%7UM9.DY43$]"+5@]3T5?/%I!(DL_5SDF7$]?8"TB2S-&0RM8
+M,2<X/SU?0DA7*U]@*39,1B,V30I-+TXM2S!7-#LK35Y=3DI035T[3%95.T(O
+M7T`K-3<J(3Y7-%DV2R]$3R4W7BDZ.DY),%L_13D_7&`^.DE9"DTP+2E/139*
+M7$=;4UM5.5Q62C!+7$LR,4M?3RTV,SY>+4\C74)3*R]*54]<8"M?/3L]7UHC
+M,5)`/2Q/7&`*33U(5RM<53X\5T]1/%PN5E]24%\Z*T%/7D8Z7U]@(34\5S]<
+M8%%&3D8U151;,SA85CM?14DL5EM?8"P]2@I-.3)"+T4O.$DF2T9=-%5*5T8V
+M,S-8)EQ:,UM++5\C)S="+DH^+2\D-DDA2%M+-2E(55]965%?3E5?4",G"DTZ
+M6TLQ*%LO,UTE3#5#.SQ40DLI*5\D4RQ254=;,T9>)"D^6CM'14I44EXY*%1'
+M6T]>735"(SLG*2E9.DH*34]3-4=?.BDF1#=26#U*6S9>.C%-32Y#7%%)*2\L
+M1E]"7EI/4E1!)D%94DLY7T\I)3=9.E<M*TY(5CM9-0I-1#M?.E954DP_15%6
+M7U!@3#Y934]72DM73U%@33TC1#HF+B=;,RQ+/SU7.S4V0T9#)#0X4D5<*5=-
+M5TI/"DU`0EI&1%0^64PE.E]$0UXZ*SM<5EM?/E<U4S0G02<Q7T9.*R):3B@U
+M.UDU.E5.+U]@)CI/*5954B\U3R<*32==23-+/3`Q4S(_/4TV7SXK7$]<+3Y*
+M.CM80D$V4TU8.5L^)B98-B\]*2,Z4%XW)R9/7&`^5E]73UM5,PI-)T5$-2M&
+M0BY%7"Y:0$]<8%%;/BE+43]=1EA!-CD_7&!16D0F15XH0"P_5DTY1U=>4E4E
+M6$-<-#8^0E8Z"DU7)$,[3UM,.E\^1$M20EM</%XH.TI;14<R7CHI-SLH,2U6
+M)5Y%-%DQ,2PX4D0L358[4EM#.U\D2S5%4TP*35=624U75T9+,DU)3RE<4C,[
+M754V2RM.-E<W,RTW131?0CE:3$D^6R1"3U97-UQ+/ULU-29*4SY?7#\\2PI-
+M/ULU,"U4.DY*+E%352]"1"U+.5PL.T];.T-67RM>6T1?4",Q.S5#-4M>)R4[
+M7E8H7U`C/3M?5"5*-3-8"DTF)2]86$8_/$,]/3(M7R)3+30G7V!@*DE;4U]#
+M6#M55RTU.SI5-2=8,RHW42-/14A7-%9"2BDB1S161S0*36`R*2Q4/U,Q3%)-
+M7TQ--4M>5DTR5DI/74@W-U)?4",S.4I(5#4W+2@C,#<V+3)>75\Z)55?4"%?
+M1DHN,PI--3TP1%]0(3D^-R,_6U1#-34W648U.BDE7CI#1D0G*B%.*2=>72A3
+M/UM3-%<],SHJ1"%66D,],SHJ(D(S"DU3)CE/1DM62B$_3$U+.E!*3E=254,W
+M7U%63R4X-5<S,4M?/CI/.2E%1#L[64165UU9758O63I4248],$P*33(S*R<L
+M3EHQ-5XU2CLG/#M)22\K0SE/1EY=7R,T.E%12SPK33=93RM>7%4R*E=;4C%&
+M.SM<4E]>0%4Q8`I-*E13-D]3,BLG7TQ+7%4^-E<^64M:.357+4TZ3S)%3B5>
+M4DU9.DTI7$]<+3XZ5UP\1"Y*-S)2*TT[4R9>"DTV3%I?4$<U0$]#*EY'45<U
+M44HZ2SHQ4BT\.TU27C==6D\P33!<,C]6*SA07SI5-C):5E]:339>7%=<-U\*
+M36!@)DPS4"$L2E=5629.+RXY/5=>7$T]2SQ>)UTK53HQ24M',U5$3B9>7%);
+M13I+25Y;)T9".#5)/%1.-@I-,5DE2E9+5S9,-T,W)5)77E)*2UU6+3];2E4T
+M-CA*2"4[-T)#,BL_,ETV/B94-45#7TY,5EL[-B@H7BI0"DU$.S1=0S]#7V`N
+M+SI87U<V7U`C,#I263]>+T%**BI92CY;+D8_5C%-7U`C7#]45UY=-3T_3S0T
+M-4Q/02P*33I'43(F54H^)T]<8$0K45];2S]>0$TQ,3$O6"8E+UA(12\T/UXO
+M4C-?/DI*524E.E%>)%)%7#`Q7SXV1@I-5#0T)$-**BHJ8")'*S$Q,&!9.BDJ
+M*BI@)E15.BHJ(D)3.$I+,U%;0"-<5SPU53]"+E%-*#@X7RI-."1>"DTW7B8L
+M(D)"0$19,E1@7TTB)T-?14A/7&!:)3=,/%=715]0(DY%)24Z-%(Y0S9?55=?
+M.RU*25!?+S\R.T\*349>.T]37R,Q,351+%DE23]>+S=?8"@B53PA2U@G74LW
+M1R=<,S0T-44U7B172#\D+%PE/#XI2S\G)U=/7PI$8"0F3SHK.U]@)3Q'7ELQ
+M,34Q7B1"25PQ62]85U]0(C!453Q+,3$V)%U3-D5,/U\Y"F`*96YD"@I>7EY>
+H7@H*5V5L;"P@=&AE<F4@:7,@=&AE('!I8R$A(0H*"2T@26=O<BX*"@``
+`
+end
diff --git a/webstump/tmp/demo.mime.txt b/webstump/tmp/demo.mime.txt
new file mode 100644 (file)
index 0000000..43c09c3
--- /dev/null
@@ -0,0 +1,845 @@
+From ichudov
+From: devnull
+Subject: Negation: The truth about AntiOnline::JHEKEYTGWJH
+X-Moderate-For: demo.newsgroup
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+Return-Path: <test-user@aol.com>
+Date: Thu, 20 May 1999 15:22:29 +0200 (CEST)
+Message-Id: <199905201322.PAA19186@mail.replay.com>
+From: JaneDoe@aol.com (Jane Doe)
+Newsgroups: demo.newsgroup
+Subject:  I used to live in this building
+
+
+I am attaching a picture of a building where I used to live. I had
+some very good times there.
+
+If anyone has pictures of other interesting buildings, please send them to me.
+
+begin 644 building.gif
+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
+>EW*NUXW*&F*V9&BRJJ:V:;!2*9;&:8J:IUP$!``[
+`
+end
+
+I hope that you like the picture.
+
+Jane
diff --git a/webstump/tmp/demo.text.txt b/webstump/tmp/demo.text.txt
new file mode 100644 (file)
index 0000000..a7c9cb7
--- /dev/null
@@ -0,0 +1,33 @@
+From ichudov
+From: devnull
+Subject: Negation: The truth about AntiOnline::JHEKEYTGWJH
+X-Moderate-For: demo.newsgroup
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+Return-Path: <test-user@aol.com>
+Date: Thu, 20 May 1999 15:22:29 +0200 (CEST)
+Message-Id: <199905201322.PAA19186@mail.replay.com>
+From: JohnDoe@webtv.net (John Doe)
+Newsgroups: demo.newsgroup
+Subject: MP3 encoding, some details and corrections
+
+Someone wrote:
+
+>http://www.news.com/News/Item/0,4,37010,00.html
+>describes the plan to deal with the threat of MP3 files.
+
+As if it was anything new...
+
+>
+>In any case, at some point MP3 players will be upgraded to a "phase two".
+> In this mode they can play music files ripped from the new CDs via "secure
+> ripping".  Such files can only be played on devices owned by the person who ri
+pped
+> them, apparently.
+
+If you hear, see, smell, feel or taste it, it can always be, from the
+womb, unsecurely ripped.  Does a PC play MP3 in the woods?   Run a
+tap from the secure device's speaker to your sound card.  Hit record.
+Share it with your friends. Bits want to be free!
+
diff --git a/xlog/bin/record b/xlog/bin/record
new file mode 100755 (executable)
index 0000000..27ca36b
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/perl -w
+
+use strict (qw(vars));
+use IO::Handle;
+
+our %f;
+our ($how) = @_;
+
+my $dir= $0;
+$dir =~ s,/[^/]+$,,;
+$dir =~ s,/bin$,/log,;
+$dir .= "/$ARGV[1]";
+
+sub parse__headerline () {
+    $f{Subject}= $' if m/^Subject:\s*/i; #';
+    $f{MessageID}= $' if m/^Message\-ID:\s*/i; #';
+    $f{From}= $' if m/^From:\s*/i; #';
+}
+sub parse__stumpsubject () {
+    $f{MessageNum}=$1 if m/^Subject:.*\:\:\w+\/(\d+)$/i;
+}
+
+sub parse_posted () {
+    while (<STDIN>) {
+       chomp;
+       parse__headerline();
+       last if m/^$/;
+    }
+    $f{Event}= 'post';
+}
+sub parse_submission () {
+    my $hadng=0;
+    while (<STDIN>) {
+       chomp;
+       $hadng++ if m/^Newsgroups:/i;
+       if (m/^$/) {
+           last if $hadng;
+           undef %f;
+       }
+       parse__headerline();
+    }
+    $f{Event}= 'receive';
+}
+
+sub parse_stump2webstump () {
+    while (<STDIN>) {
+       chomp;
+       parse__stumpsubject() unless exists $f{'MessageNum'};
+       last if m/^\@{40,}$/;
+    }
+    while (<STDIN>) {
+       chomp;
+       last if m/^$/;
+       parse__headerline();
+    }
+    $f{Event}= 'enqueue';
+}
+
+sub parse_webstump2stump () {
+    while (<STDIN>) {
+       chomp;
+       parse__stumpsubject();
+       last if m/^$/;
+    }
+    while (<STDIN>) {
+       chomp;
+       next unless m/^reject|^approve|^preapprove/;
+       $f{Event}= $_;
+       last;
+    }
+}
+
+sub parse_mailout () {
+    while (<STDIN>) {
+       chomp;
+       $f{Event}= "notify $'" if m/^X-Webstump-Event:\s*/i; #';
+       last if m/^$/;
+    }
+    while (<STDIN>) {
+       chomp;
+       next unless s/^\> //;
+       last if m/^$/;
+       parse__headerline();
+    }
+}
+
+$f{Event}= '?';
+&{"parse_$ARGV[0]"};
+while (<STDIN>) { }
+STDIN->error and die $!;
+
+$f{Now}= time;
+
+open L, ">>$dir/event.log" or die $!;
+
+my @s= map {
+    my $v= $f{$_};
+    $v= '' unless defined $v;
+    $v =~ s/\t/  /g;
+    $v =~ s/[\r\n]/?/g;
+    $v;
+} qw(Now MessageNum MessageID From Subject Event);
+
+print L join("\t",@s)."\n" or die $!;
+close L or die $!;
diff --git a/xlog/bin/report b/xlog/bin/report
new file mode 100755 (executable)
index 0000000..ee3022e
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/perl -w
+
+our ($ng,@ARGV) = @ARGV;
+chdir $ng or die $!;
+
+use strict (qw(vars));
+use IO::Handle;
+use POSIX;
+use CGI qw/:standard *table end_*/;
+
+our @lines= ();
+our @s;
+
+our ($processline,$needmap);
+our ($selectmid,$selectnum);
+
+sub processlog ($$) {
+    my ($taccat, $fn)= @_;
+    open F, "$taccat $fn |" or die $!;
+    while (<F>) {
+       chomp;
+       @s= split /\t/;
+        $s[0]= strftime "%Y-%m-%d %H:%M:%S %Z", localtime $s[0];
+       &$processline();
+    }
+}
+sub processlogs ($) {
+    my ($taccat) = @_;
+    my (@logs) = qw(event.log.0 event.log);
+    @logs= reverse @logs if $taccat eq 'tac';
+    processlog($taccat, $_) foreach @logs;
+}
+
+sub processline_print () {
+    my @sp= @s;
+    $sp[3] =~ s/\@\w{0,2}/ at .. /;
+    my @sp= map { escapeHTML($_) } @sp;
+    my @spu= map {
+       s/\W/ sprintf "%%%02x", ord $& /ge;
+       $_;
+    } @s;
+    if (length $s[1] && length $s[2]) {
+       my $url= url().'/message/'.$spu[1].'/'.$spu[2];
+       foreach my $i (qw(1 2)) {
+           $sp[$i]= a({ href=>$url }, $sp[$i] );
+       }
+    }
+#print STDERR join('|',@sp),"\n";
+    print Tr(td([@sp]));
+}
+
+sub processline_print_ifsingle () {
+    return unless $s[1] eq $selectnum
+              or $s[2] eq $selectmid;
+    processline_print();
+}
+
+our (%done_num,%done_id,%num2id,%id2num);
+sub processline_queue_prescan () {
+    my ($num,$id,$e) = @s[1..2,5];
+    if (length $id and length $num) {
+       $id2num{$id}= $num;
+       $num2id{$num}= $id;
+    }
+    return unless $e =~ m/^decide reject discard|^notify reject|^post/;
+#print STDERR "finishing $e $s[1] $s[2]\n";
+    $num= $id2num{$id} if !length $num;
+    $id= $num2id{$num} if !length $id;
+#print STDERR "finishing $e $num $id\n";
+    $done_num{$num}++ if defined $num;
+    $done_id{$id}++ if defined $id;
+}
+sub processline_queue () {
+    return if $done_num{$s[1]};
+    return if $done_id{$s[2]};
+    processline_print();
+}
+
+my $pi= path_info();
+our $title;
+
+$needmap= 0;
+$processline= \&processline_print;
+
+if ($pi =~ m,^/message/(\d+)/(.*)$,) {
+    ($selectnum, $selectmid) = ($1,$2);
+    $title= "Single message ".escapeHTML($selectmid);
+    $processline= \&processline_print_ifsingle;
+} elsif ($pi =~ m/^$/) {
+    $title= "Recent activity";
+} elsif ($pi =~ m,^/queue,) {
+    $title= "Activity regarding still-queued messages";
+    $processline= \&processline_queue_prescan;
+    processlogs('cat');
+    $processline= \&processline_queue;
+}
+
+print header(), start_html($title), h1($title), table();
+
+print Tr(td([map { strong($_) } (qw(
+                               Date
+                               Reference
+                               Message-ID
+                               From
+                               Subject
+                               Event
+                           ))]));
+
+processlogs('tac');
+
+print end_table();
+print p();
+
+print a({ href=>url() }, "All recent activity"), '; ';
+print a({ href=>url().'/queue' }, "Unfinished business");
+
+print end_html();