home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
unix
/
volume28
/
bmw-5
/
part01
/
bmw
next >
Wrap
Text File
|
1994-07-09
|
26KB
|
1,079 lines
#! /usr/bin/perl
## Set the above path to your path to perl.
##
## BMW - The Black Marble Wombat Mailing List Manager
## Copyright (c) 1994 by Clay Luther, All rights reserved.
## THIS SOFTWARE IS COVERED BY THE GNU SOFTWARE LICENSE.
## YOU MAY USE THIS SOFTWARE AS YOU PLEASE SO LONG AS YOU DO NOT REMOVE
## THE COPYRIGHT NOTICES.
##
$USAGE = "$0 <listname>";
##
## General operation.
## BMW accepts a single mail message on standard input. It expects the
## message to obey standard UN*X mail format, that is:
##
## FROMSPACE line
## HEADERS
## <blank line>
## DATA
##
## BMW will attempt to extract the user's address from the message. However,
## if the environment variable SENDER is set, BMW will use it instead.
##
## BMW will parse the mail message looking for commands from the user.
## Commands should appear either on the subject line or at the beginning of
## a line in the DATA section. The user may issue an unlimited number of
## commands.
##
## Commands are significant to 3-letters and are not case-sensitive.
## The commands are:
##
## subscribe
## unsubscribe
## who
## dir
## cd
## get
## ping
## help
## digest
## undigest
##
# BEGIN
$LF = "\n";
$TRACE = 0; # Trace debug. This will be set by the global routines.
sub trace # The trace routine.
{
local($i) = @_;
print $i,$LF if $TRACE;
}
srand;
umask("000");
if ($#ARGV != 0)
{
# We must have at least one argument!
die "USAGE: ",$USAGE,$LF;
}
$LISTNAME = $ARGV[0];
$LISTNAME =~ tr/A-Z/a-z/; # Convert everything to lower case for now.
%GLOBALS = ();
## We need to set the hostname. You this is done by querying hostname and
## domainname. You can override this here or set your FQDN global explicitly
## in the bmw.cf file.
$HOSTNAME = `hostname`; chop($HOSTNAME);
$DOMAINNAME = `domainname`; chop($DOMAINNAME);
## Path to bmw.cf - CHANGE THIS FOR YOUR SYSTEM
#$BMWCF = "/etc/bmw.cf";
$BMWCF = "/home/bmw/bmw.cf";
## Process the config file and learn about all the globals.
## FORMAT OF CONFIG FILE:
## Each line of the config file should have the format
## <globalname> = <value>
## for example,
## BASEDIR = /usr/local/lib/bmw
##
## I now set the defaults. You should override these in your bmw.cf file.
##
$GLOBALS{'SENDMAIL'} = "/usr/lib/sendmail";
$GLOBALS{'SENDMAILOPTS'} = "";
$GLOBALS{'ENCODE'} = "/usr/bin/uuencode";
$GLOBALS{'COMPRESS'} = "/bin/gzip";
$GLOBALS{'COMPSUFFIX'} = ".gz";
$GLOBALS{'ARCDIR'} = "/home/ftp/pub/lists";
$GLOBALS{'ARCOWNER'} = "ftp";
$GLOBALS{'OWNER'} = "postmaster";
$GLOBALS{'DEBUG'} = 0;
$GLOBALS{'LOG'} = 0;
$GLOBALS{'MAXGETS'} = 5;
$GLOBALS{'PREFERFTP'} = 0;
$GLOBALS{'TRACE'} = 0;
$GLOBALS{'BASEDIR'} = "/usr/local/lib/bmw";
$GLOBALS{'FQDN'} = $HOSTNAME . "." . $DOMAINNAME;
$GLOBALS{'DIGEST'} = 0;
$GLOBALS{'TMPDIR'} = "/var/tmp";
$GLOBALS{'USER'} = "bmw";
$GLOBALS{'GROUP'} = "bin";
@VALIDGLOBALS = ("SENDMAIL", "SENDMAILOPTS", "TMPDIR", "ENCODE",
"COMPRESS", "COMPSUFFIX", "ARCDIR", "ARCOWNER",
"OWNER", "DEBUG", "LOG", "MAXGETS", "PREFERFTP",
"TRACE", "BASEDIR", "FQDN", "DIGEST", "USER", "GROUP");
sub validGlobal {
## Determine if the string is a valid global reference.
local($s) = @_;
local($i);
for ($i=0; $i<=$#VALIDGLOBALS; $i++) {
if ($s eq $VALIDGLOBALS[$i]) { return 1; }
}
return 0;
}
## Load the globals from the cf file.
if (-e $BMWCF) {
local($lno) = 0;
local(@LINE);
open(CF, "<$BMWCF") || die "Cannot open $BMWCF: $!\n";
while (<CF>) {
$lno++;
chop;
tr/\t //d;
@LINE = split("=");
if (!$LINE[0]) { die "ERROR in $BMWCF line $lno; $_\n"; }
$LINE[0] =~ tr/a-z/A-Z/;
if (!&validGlobal($LINE[0])) {
die "ERROR in $BMWCF line $lno; Unknown global \"$LINE[0]\"\n";
}
## Good. We have a valid global
$GLOBALS{$LINE[0]} = $LINE[1];
}
}
else {
die "$BMWCF does not exist!\n";
}
if ($GLOBALS{'DEBUG'}) {
local($key);
foreach $key (keys %GLOBALS) {
print "$key = $GLOBALS{$key}",$LF;
}
}
## Internal global values. Do not fiddle with these without good reason.
$VERSION = "5.0";
$LISTDIR = $GLOBALS{'BASEDIR'} . "/$LISTNAME";
$LISTARC = $GLOBALS{'ARCDIR'} . "/$LISTNAME";
$LISTFILE = "$LISTDIR/subscribers";
$DIGESTFILE = "$LISTDIR/subscribers.d";
$LOGFILE = "$LISTDIR/log";
$MESSAGE = $GLOBALS{'BASEDIR'} . "/message";
$MESSAGE = "$LISTDIR/message" if (-e "$LISTDIR/message");
$HELP = $GLOBALS{'BASEDIR'} . "/help";
$HELP = "$LISTDIR/help" if (-e "$LISTDIR/help");
$WDIR = ""; # working directory suffix for gets
$TMPFILE = $GLOBALS{'TMPDIR'} . "/bmw$$";
$REPLYFILE = "$TMPFILE.reply";
$TMPLOGFILE = "$TMPFILE.log";
$LISTOWNER = "$LISTNAME-owner";
$LISTREQUEST = "$LISTNAME-request";
@ERRORS = ();
%LOCKS = ();
##
## Exit routines. These make sure everything is cleaned up.
##
sub finish {
local($rc) = @_;
local($i);
system("rm -f $TMPFILE $REPLYFILE $TMPLOGFILE");
if ($#ERRORS > -1) {
open(T, ">$TMPFILE");
print T "From: $LISTOWNER\n";
print T "Subject: Errors from $LISTREQUEST\n";
print T "To: $LISTOWNER\n\n";
for ($i = 0; $i <= $#ERRORS; $i++) {
print T $ERRORS[$i],$LF;
}
close(T);
system("cat $TMPFILE | $GLOBALS{'SENDMAIL'} \'$LISTOWNER\'");
system("rm -f $TMPFILE")
}
exit($rc);
}
sub DIE {
local($msg) = @_;
print STDERR "$msg",$LF;
$ERRORS[$#ERRORS+1] = $msg;
local($key);
foreach $key (keys %LOCKS) {
system("rm -f $key");
}
&finish(-1);
}
# Test for things that must exist.
&DIE("$LISTDIR does not exist!\n") if (! -d $LISTDIR);
if (! -e $LISTFILE) {
## Create a list file.
open(LF,">$LISTFILE") || &DIE("Could not create $LISTFILE: $!\n");
print LF "$LISTOWNER\n";
close(LF);
chown $GLOBALS{'USER'}, $GLOBALS{'GROUP'}, $LISTFILE;
chmod 0664,$LISTFILE;
}
if (! -e $DIGESTFILE) {
## Create a digest file.
open(LF,">$DIGESTFILE") || &DIE("Could not create $DIGESTFILE: $!\n");
print LF "$LISTOWNER\n";
close(LF);
chown $GLOBALS{'USER'}, $GLOBALS{'GROUP'}, $DIGESTFILE;
chmod 0664,$DIGESTFILE;
}
if (! -d $LISTARC) {
$ARCTHERE = 0;
}
else {
$ARCTHERE = 1;
}
## Start parsing the message.
$FROM = ""; ## who from?
$OFROM = ""; ## original from? overrides From:
$REPLYTO = ""; ## reply to? overrides Originally-From:
$SUBJECT = ""; ## subject line
$REPLYOPEN = 0; ## is the reply file open?
$STOPEN = 0; ## used for sending text files
$LOGOPEN = 0; ## is the logfile open?
$NUMGETS = 0; ## how many gets so far?
$HEADERS = 1; ## location of the parse
$INFROM = 0; ## deal with problem Froms:
@COMMANDS = ();
shift;
while (<>) {
if ($HEADERS) {
if ($INFROM && /^\s/) {
local($morefrom);
$morefrom = $1 if m/^\s*(.*)\n/;
$FROM = $FROM . $morefrom;
}
else {
$INFROM = 0;
if (/^Subject:/) { $SUBJECT = $1 if m/^Subject:\s*(.*)\n/; }
if (/^From:/) { $FROM = $1 if m/^From:\s*(.*)\n/; $INFROM = 1; }
if (/^Original-From:/) { $OFROM = $1 if m/^Original-From:\s*(.*)\n/; }
if (/^Reply-To:/) { $REPLYTO = $1 if m/^Reply-To:\s*(.*)\n/; }
if ($_ eq "\n") {
## Blank line ends headers.
$HEADERS = 0;
&debug("Subject = $SUBJECT\n") if $SUBJECT;
&loadCommand($SUBJECT) if $SUBJECT;
}
}
}
else {
&loadCommand($_);
}
}
## We have attempted to parse the entire message. First, let's fix up
## set the $ADDRESS, which will be used in the rest of the program.
## $ADDRESS is the address of the person using the bmw at the moment.
$ADDRESS = "";
if ($ENV{'SENDER'}) {
$ADDRESS = $ENV{'SENDER'};
}
else {
$ADDRESS = $FROM if $FROM;
$ADDRESS = $OFROM if $OFROM;
$ADDRESS = $REPLYTO if $REPLYTO;
$ADDRESS = &fixAddress($ADDRESS);
}
&debug("Address = $ADDRESS\n");
if (!$ADDRESS) {
&DIE("Invalid address: $ADDRESS\n");
}
## Commands loaded, a good address. What more could we want?
&processCommands();
&finish(0);
## WE'RE DONE!
#### SUBROUTINES BELOW ####
sub fixAddress {
## fixAddress attempts to expunge an address string of all extraneous
## text except for the actual address. It assumes that addresses have the
## forms:
##
## <address> username
## username <address>
## (username) address
## address (username)
local($a) = @_;
$_ = $a;
if ($a =~ /.*\<.*\>.*/) {
## <> format? pretty easy.
$a = $1 if m/.*\<(.*)\>.*/;
}
elsif ($a =~ /.*\(.*\).*/) {
$a = ($1 . $2) if m/(.*)\(.*\)(.*)/;
}
$_ = $a;
tr/ \t//d;
return $_;
}
sub loadCommand {
## parse the parameter.
local($_) = @_;
$COMMANDS[$#COMMANDS+1] = "SUB" if m/^\s*sub.*/i;
$COMMANDS[$#COMMANDS+1] = "UNS" if m/^\s*uns.*/i;
$COMMANDS[$#COMMANDS+1] = "WHO" if m/^\s*who.*/i;
$COMMANDS[$#COMMANDS+1] = "DIR $1" if m/^\s*dir\s*(\S*)/i;
$COMMANDS[$#COMMANDS+1] = "GET $1" if m/^\s*get\s*(\S*)/i;
$COMMANDS[$#COMMANDS+1] = "HEL" if m/^\s*hel.*/i;
$COMMANDS[$#COMMANDS+1] = "PIN" if m/^\s*pin.*/i;
$COMMANDS[$#COMMANDS+1] = "DIG" if m/^\s*dig.*/i;
$COMMANDS[$#COMMANDS+1] = "UND" if m/^\s*und.*/i;
$COMMANDS[$#COMMANDS+1] = "CD $1" if m/^\s*cd\s*(\S*)/i;
}
sub processCommands {
if ($#COMMANDS > -1) {
local($i);
&startReply();
for ($i=0; $i <= $#COMMANDS; $i++)
{
$_ = $COMMANDS[$i];
if (/^SUB/) { &doSub($_); }
elsif (/^UNS/) { &doUns($_); }
elsif (/^WHO/) { &doWho($_); }
elsif (/^DIR/) { &doDir($_); }
elsif (/^GET/) { &doGet($_); }
elsif (/^HEL/) { &doHel($_); }
elsif (/^PIN/) { &doPin($_); }
elsif (/^DIG/) { &doDig($_); }
elsif (/^UND/) { &doUnd($_); }
elsif (/^CD/) { &doCD($_); }
else {
&reply("Unknown command: $_.\nNow how'd that happen?\n");
&error("Unknown command: $_\n");
}
}
}
else {
&standardReply();
}
&sendReply();
}
sub standardReply {
&startReply();
&reply("\nI could not detect any commands in your mail to me.\n" .
"Need help? Set your subject line to HELP.\n");
&sendReply();
}
sub startReply {
local($FQDN) = $GLOBALS{'FQDN'};
if ($REPLYOPEN) { return; }
open(REPLY, ">$REPLYFILE") || &DIE("Cannot open $REPLYFILE: $!\n");
print REPLY "To: $ADDRESS\n";
print REPLY "Subject: Reply from $LISTREQUEST\n";
print REPLY "Errors-To: $LISTOWNER@$FQDN\n";
print REPLY "Reply-To: $LISTREQUEST@$FQDN\n";
print REPLY "From: $LISTOWNER@$FQDN\n";
print REPLY "Sender: $LISTOWNER@$FQDN\n";
print REPLY "X-bmw: Black Marble Wombat Version $VERSION\n";
print REPLY "X-list: $LISTNAME@$FQDN\n\n";
if (-e $MESSAGE)
{
local($listname) = $LISTNAME;
$listname =~ tr/a-z/A-Z/;
open(MESSAGE, "<$MESSAGE") || &DIE("Cannot open $MESSAGE: $!\n");
while (<MESSAGE>)
{
s/LISTNAME/$listname/g;
s/LISTOWNER/$LISTOWNER@$FQDN/g;
s/LISTREQUEST/$LISTREQUEST@$FQDN/g;
s/LISTADDR/$LISTNAME@$FQDN/g;
print REPLY $_;
}
close(MESSAGE);
}
else {
&error("No message file for $LISTNAME.\n");
print REPLY "Black Marble Wombat, Version $VERSION\n";
}
print REPLY "\n" .
"List addresses:\n" .
" Post messsages to -> $LISTNAME@$FQDN\n" .
" Automatic administration -> $LISTREQUEST@$FQDN\n" .
" List owner (a person) -> $LISTOWNER@$FQDN\n";
print REPLY "\nHello $ADDRESS.\n";
$REPLYOPEN = 1;
}
sub sendReply {
if (!$REPLYOPEN) { &startReply(); }
&reply("\nThe Black Marble Wombat Mailing List Manager, " .
"Version $VERSION\n" .
"By Clay Luther, clay@gojira.monsta.com\n" .
"Copyright (c) 1994 Monsta, Inc.\n");
close(REPLY);
$REPLYOPEN = 0;
&Mail($REPLYFILE);
}
sub reply {
local($s) = @_;
if (!$REPLYOPEN) { &startReply(); }
print REPLY $s;
}
sub Mail {
## Send a file by mail.
local($f) = @_;
local($mailstr) = $GLOBALS{'SENDMAIL'} . " " .
$GLOBALS{'SENDMAILOPTS'} . " " .
$ADDRESS;
if (-r $f) {
local($cmd) = `cat $f | $mailstr`;
$cmd =~ tr/\s//d;
&error("Mail command ($mailstr) returned $cmd\n") if $cmd;
}
else {
&error("Could not read $r\n");
}
}
sub error {
local($e) = @_;
$ERRORS[$#ERRORS+1] = $e;
}
##
## Command processing
##
sub doPin {
&reply("\nCommand: PING\n");
&reply("The Black Marble Wombat, Version $VERSION\n");
&log("PIN $ADDRESS");
}
sub doWho {
## Attempt to display the list (and digest list) subscribers.
local($cnt) = 0;
&reply("\nCommand: WHO\n");
if (open(LF, "<$LISTFILE")) {
while (<LF>) { &reply($_) && $cnt++ if (!/$LISTOWNER/); }
close(LF);
}
else {
&error("doWho: could not open $LISTFILE: $!\n");
}
if ($GLOBALS{'DIGEST'} && -e $DIGESTFILE)
{
if (open(LF, "<$DIGESTFILE")) {
while (<LF>) { &reply($_) && $cnt++ if (!/$LISTOWNER/); }
close(LF);
}
else {
&error("doWho: could not open $DIGESTFILE: $!\n");
}
}
&reply("\n$cnt subscriber(s).\n");
}
sub doSub {
## Allow the user to subscribe to the mailing list or its digest.
&reply("\nCommand: SUBSCRIBE\n");
if ($GLOBALS{'DIGEST'} && &inList($DIGESTFILE)) {
if (&removeFromList($DIGESTFILE)) {
&reply("You have been removed from the digest list.\n")
}
else {
&reply("There was an error on this end and you were not removed\n" .
"from the digest list.\n");
}
}
if (!&inList($LISTFILE)) {
if (&addToList($LISTFILE)) {
&reply("You have been added to the mailing list.\n");
&log("SUB $ADDRESS");
}
else {
&reply("There was an error on this end and you were not added\n" .
"to the mailing list.\n");
}
}
else {
&reply("You are already subscribed to the mailing list.\n");
}
}
sub doDig {
&reply("\nCommand: DIGEST\n");
if ($GLOBALS{'DIGEST'}) {
if (&inList($LISTFILE)) {
if (&removeFromList($LISTFILE)) {
&reply("You have been removed from the mailing list.\n");
}
else {
&reply("There was an error on this end and you were not removed\n" .
"from the mailing list file.\n");
}
}
if (!&inList($DIGESTFILE)) {
if (&addToList($DIGESTFILE)) {
&reply("You have been added to the digest list.\n");
&log("DIG $ADDRESS");
}
else {
&reply("There was an error on this end and you were not added\n" .
"to the digest list.\n");
}
}
else {
&reply("You are already on the digest list.\n");
}
}
else {
&reply("This list does not currently support a digest format.\n");
}
}
sub doUns {
&reply("\nCommand: UNSUBSCRIBE\n");
if (&inList($LISTFILE)) {
if (&removeFromList($LISTFILE)) {
&reply("You have been removed from the mailing list.\n");
&log("UNS $ADDRESS");
}
else {
&reply("There was an error on this end and you were not removed\n" .
"from the mailing list.\n");
}
}
else {
&reply("I could not find your address:\n" .
" $ADDRESS\n" .
"in the mailing list file. Perhaps you subscribed from a\n" .
"different address or you need to send an UNDIGEST request\n" .
"instead.\n");
&doOther($DIGESTFILE);
}
}
sub doUnd {
&reply("\nCommand: UNDIGEST\n");
if ($GLOBALS{'DIGEST'}) {
if (&inList($DIGESTFILE)) {
if (&removeFromList($DIGESTFILE)) {
&reply("You have been removed from the digest list.\n");
&log("UND $ADDRESS");
}
else {
&reply("There was an error on this end and you were not removed\n" .
"from the digest list.\n");
}
}
else {
&reply("I could not find your address:\n" .
" $ADDRESS\n" .
"in the digest list file. Perhaps you subscribed from a\n" .
"different address or you need to send a UNSUBSCRIBE request\n" .
"instead.\n");
&doOther($LISTFILE);
}
}
else {
&reply("The mailing list does not support a digest format.\n");
}
}
sub doOther {
local($OTHERFILE) = @_;
if (-e $OTHERFILE) {
&reply("\nAttempting to find you in the other list...\n");
if (&inList($OTHERFILE)) {
if (&removeFromList($OTHERFILE)) {
&reply("\nYou have been removed from the other list.\n");
&log("OTH $ADDRESS");
}
else {
&reply("There was an error on this end and you were not removed\n" .
"from the other list.\n");
}
}
else {
&reply("\nI could not find your address:\n" .
" $ADDRESS\n" .
"in the other list file. Please feel free to contact the list owner,\n" .
" $LISTOWNER@$FQDN\n" .
"for help.\n");
}
}
else {
&reply("The mailing list does not support the other format.\n");
}
}
sub inList {
local($l) = @_;
if (open(LF,"<$l")) {
while (<LF>) {
if (/$ADDRESS/i) {
close(LF);
return 1;
}
}
close(LF);
}
else {
&error("inList: could not open $l: $!\n");
}
return 0;
}
sub addToList {
local($l) = @_;
local($ret) = 0;
local($cmd);
if (&lockFile($l)) {
$cmd = `cp $l $l.bak`; chop($cmd);
chmod 0664, "$l.bak";
chown $GLOBALS{'USER'},$GLOBALS{'GROUP'}, "$l.bak";
&error("addToList: Cmd returned $cmd\n") if $cmd;
if (open(LF,">>$l")) {
print LF $ADDRESS . $LF;
close(LF);
$ret = 1;
}
else {
&error("addToList: could not open $l: $!\n");
}
&unlockFile($l);
}
else {
&error("addToList: $l locked!\n");
}
return $ret;
}
sub removeFromList {
local($l) = @_;
local($ret) = 0;
if (&lockFile($l)) {
## move the list to a temp file, then open it and copy it back, except for
## the user we want to delete.
system("cp $l $TMPFILE");
if (open(TMPFILE, "<$TMPFILE")) {
if (open(LF, ">$l")) {
while (<TMPFILE>) {
print LF $_ if (!/$ADDRESS/i)
}
close(TMPFILE);
close(LF);
$ret = 1;
}
else {
&error("removeFromList: could not open $l: $!\n");
close(TMPFILE);
}
}
else {
&error("removeFromList: could not open $TMPFILE: $!\n");
}
&unlockFile($l);
}
else {
&error("removeFromList: $l locked!\n");
}
return $ret;
}
sub doDir {
local($v) = @_;
local($cmd, $p) = split(/ /,$v);
## Show the user a directory listing.
&reply("\nCommand: DIR $p\n");
if (-d $LISTARC) {
local($dir);
if ($WDIR) {
$dir = $LISTARC . "/" . $WDIR . "/" . $p;
}
else {
$dir = $LISTARC . "/" . $p;
}
if ($dir =~ /\.\./) {
&reply("Invalid directory.\n");
&log("INVDIR $ADDRESS, $p");
&error("$ADDRESS attempted a dir of $p.\n");
}
elsif (-d $dir) {
## Good directory case
if (opendir(DIR, $dir)) {
rewinddir(DIR);
local(@dirl) = readdir(DIR);
local($fpath);
@dirl = sort(@dirl);
while ($dirl[0]) {
if ($dirl[0] !~ /^\./ && $dirl[0] !~ /^\.\./) {
$fpath = $dir . "/" . $dirl[0];
if (-d $fpath) { &reply("d $dirl[0]\n"); }
else { &reply("f $dirl[0]\n"); }
}
shift(@dirl);
}
closedir(DIR);
&reply("--\n");
&log("DIR $ADDRESS");
}
else {
&reply("There was an error on this end.\n");
&error("doDir: could not opendir $dir: $!\n");
}
}
else {
&reply("Invalid directory.\n");
}
}
else {
&reply("The list does not currently support archives.\n");
}
}
sub doCD {
local($v) = @_;
local($cmd, $cdir) = split(/ /, $v);
&reply("\nCommand: CD $cdir\n");
if (-d $LISTARC) {
if ($cdir) {
if ($cdir !~ /\.\./) {
local($wpath);
if ($WDIR) {
$wpath = $LISTARC . "/" . $WDIR . "/" . $cdir;
}
else {
$wpath = $LISTARC . "/" . $cdir;
}
if (-d $wpath) {
$WDIR = $cdir;
&reply("Working directory set to $WDIR\n");
}
else {
&reply("Invalid directory.\n");
}
}
else {
&reply("Invalid directory specification.\n");
&log("INVDIR $ADDRESS, $cdir");
&error("$ADDRESS attempted a CD $cdir.\n");
}
}
else {
$WDIR = "";
&reply("Working directory reset.\n");
}
}
else {
&reply("The list does not currently support archives.\n");
}
}
sub doGet {
## Get a file from the archives.
local($v) = @_;
local($cmd, $f) = split(/ /, $v);
&reply("\nCommand: GET $f\n");
if (-d $LISTARC) {
local($fname);
if ($WDIR) {
$fname = $LISTARC . "/" . $WDIR . "/" . $f;
}
else {
$fname = $LISTARC . "/" . $f;
}
local(@ff) = split(/\//, $f);
local($ffname) = $ff[$#ff]; # This is the "absolute" name of the file
if ($f !~ /\.\./ && $f !~ /^\./) {
if (-e $fname && ! -d $fname && -r $fname) {
&reply("Sending file '$ffname'...\n");
&sendFile($fname, $ffname);
&reply("--\n");
&log("GET $ADDRESS, $f");
}
else {
&reply("That file does not exist.\n");
}
}
else {
&reply("Invalid file specification.\n");
&error("$ADDRESS attempted GET $f\n");
&log("INVFIL $ADDRESS, $f");
}
}
else {
&reply("The list does not currently support archives.\n");
}
system("rm -f $TMPFILE.st");
}
sub sendFile {
local($f, $ff) = @_;
if (-T $f) {
&sendTextFile($f, $ff);
}
else {
&sendBinaryFile($f, $ff);
}
}
sub sendTextFile {
# We have been passed the name of a text file to send. This text file
# might be very large (greater than 30000 bytes). If so, break it up
# and send the chunks.
local($f, $ff) = @_;
local($fsize) = -s $f;
&debug("$f, $ff, $fsize\n");
if ($fsize > 30000) {
local($pnum) = 1;
local($maxnum) = int(0.5 + ($fsize / 30000));
local($psize) = -1;
if (open(TF, "<$f")) {
while (<TF>) {
if ($psize < 0) { # first time through
&debug("psize == -1\n");
&startST($pnum, $maxnum, $ff);
&printST("---cut here---\n");
$psize = 0;
}
$psize += length();
&printST($_);
if ($psize > 30000) {
&printST("---cut here---\n");
&sendST();
&reply("Package $pnum/$maxnum sent.\n");
$pnum += 1;
$psize = -1;
&debug("Sending $ff $pnum/$maxnum $psize\n");
}
}
close(TF);
if ($STOPEN) {
&sendST();
&reply("Package $pnum/$maxnum sent.\n");
}
}
else {
&reply("There was an error on this end.\n");
&error("sendTextFile: could not open $f: $!\n");
}
}
else {
&startST(1, 1, $ff);
if (open(TF,"<$f")) {
while (<TF>) { &printST($_) };
close(TF);
&sendST();
}
else {
&error("sendTextFile: could not open $f: $!\n");
&reply("There was an error on this end.\n");
}
}
}
sub startST {
local($n1, $n2, $ff) = @_;
local($FQDN) = $GLOBALS{'FQDN'};
if ($STOPEN) { return 1; }
if (open(ST, ">$TMPFILE.st")) {
&debug("$TMPFILE.st opened.\n");
print ST "From: $LISTREQUEST@$FQDN\n";
print ST "To: $ADDRESS\n";
print ST "Sender: $LISTOWNER@$FQDN\n";
print ST "Errors-To: $LISTOWNER@$FQDN\n";
print ST "Subject: $ff: Part $n1 of $n2\n\n";
$STOPEN = 1;
}
else {
&error("startST: could not open $TMPFILE.st: $!\n");
}
return $STOPEN;
}
sub printST {
local($s) = @_;
if ($STOPEN) {
print ST $s;
}
}
sub sendST {
local($cmd, $ret);
if ($STOPEN) {
close(ST);
$cmd = $GLOBALS{'SENDMAIL'} . " " . $ADDRESS;
$ret = `cat $TMPFILE.st | $cmd`; chop($ret);
if ($ret) {
&error("sendST: cmd returned $ret.\n");
}
&debug("Sent text file.\n");
}
system("rm -f $TMPFILE.st");
$STOPEN = 0;
}
sub sendBinaryFile {
# When sending a binary file, we must convert it to text with an encoding
# program, then send it to the user as text.
local($f, $ff) = @_;
local($cmd, $ret);
&reply("This file was converted with uuencode.\n");
$cmd = $GLOBALS{'ENCODE'} . " " . $f . " " . $ff . " > " . $TMPFILE;
# The cmd should look like "uuencode /dir/dir/filename filename > tmpfile"
$ret = `$cmd`; chop($ret);
if ($ret) {
&error($GLOBALS{'ENCODE'} . " returned $ret.\n");
&reply("There was an error on this end.\n");
}
&sendTextFile($TMPFILE, $ff);
}
sub doHel {
&reply("\nCommand: HELP\n");
if (-e $HELP) {
if (open(HELP,"<$HELP")) {
while (<HELP>) { &reply($_); }
close(HELP);
&log("HEL $ADDRESS");
}
else {
&reply("There was an error on this end.\n");
&error("doHel: could not open $HELP: $!\n");
}
}
else {
&reply("Sorry, there is no help currently available.\n");
}
}
sub lockFile {
local($f) = @_;
local($lockf) = $f . ".LOCK";
local($backoff) = 5;
local($boc) = 0;
while (-e $lockf) {
sleep(5);
$boc++;
if ($boc > $backoff) {
&debug("Lock file collision!\n");
return 0;
}
}
system("touch $lockf");
chmod 0664, $lockf;
chown $GLOBALS{'USER'}, $GLOBALS{'GROUP'}, $lockf;
$LOCKS{$lockf} = 1;
&debug("$f locked.\n");
return 1;
}
sub unlockFile {
local($f) = @_;
local($lockf) = $f . ".LOCK";
if (-e $lockf) { system("rm -f $lockf"); }
delete $LOCKS{$lockf};
&debug("$f unlocked.\n");
}
sub log {
local($s) = @_;
return if (!$GLOBALS{'LOG'});
if (&lockFile($LOGFILE)) {
if (! -e $LOGFILE) {
system("touch $LOGFILE");
chown $GLOBALS{'USER'}, $GLOBALS{'GROUP'}, $LOGFILE;
chmod 0664,$LOGFILE;
}
if (open(LOGFILE, ">>$LOGFILE")) {
local($date) = `date`; chop($date);
print LOGFILE $date . ": " . $s . "\n";
close(LOGFILE);
}
else {
&error("log: could not open $LOGFILE: $!\n");
}
&unlockFile($LOGFILE);
}
else {
&error("log: $LOGFILE locked!\n");
}
}
sub debug {
local($s) = @_;
print $s if $GLOBALS{'DEBUG'};
}