#!/usr/bin/perl # #NAME # Send - Heuristic Direct Mail Delivery # #SYNOPSIS # Send [-options] [[file] ].. [to ..] [list ..] # #DESCRIPTION # This program sends its stdin or a list of files. If the keywords # "to" or "list" are present, then an explicit list of recipients # will be used, and the files' headers will be ignored. Otherwise, # the file(s) will be examined for "To:" lines. Also, if the "list" # keyword is present, the names after it are looked up in the # $HOME/mail/list/ directory, where you can keep mailing lists, one # per file. # # No other mailer is invoked by this program; the mail is sent by # making a direct TCP connection to port 25 and talking the (E)SMTP # protocol. At verbose level 2 or greater, the entire SMTP # conversation will be written to stdout, so you can see what the # other end really said. This can help tremendously in trying to # figure out why things didn't work. # # If "list" is used, this program looks in $HOME/mail/list/ for the # list names. So to create a "foo" list, create $HOME/mail/list/foo # and fill it with email addresses. They should be one per line, in # any of the common To: line formats. As usual, lines starting with # '#' are ignored. All other lines are treated as recipients. # # As a special kludge, if the keyword "file" is used before a list of # file names, the files' headers will not be scanned. This may be # used to override the program's tendency to scan headers if no # recipients can be found on the command line or in any mailing list # files. # #OPTIONS # We have a few options, flagged by '-' or '+'. For some options, '-' # means "disable" and '+' means "enable"; for others there is no such # concept and you may use either. If a parameter is used, it should # immediately follow the option letter without a space. # # -F # From . This is the source address to report to the other # end's mailer. This is useful when forwarding messages, as well as # for spoofing someone else's email. The most useful use is for # mailing lists that restrict your email to your list address. This # lets you send messages from that address even if you're on # another machine. # # -send No sends; just testing # +send Send messages (default) # # -S # Add the string to each message as a Subject: line. The # default is to send the Subject: line that's in the file. This # option adds a Subject: line to each file. # # -sig No .signature file # +sig Append $HOME/.signature (default) # $sigfl = 1; # Whether to read .signature file $sigfile = $ENV{'HOME'} . '/.signature'; # # -T # +T # Timeout interval (seconds). Default is 30 sec. # #REQUIRES # The following modules are needed. Any *.pm file listed here should # be in the same directory where you found this program. You should # make sure that @INC includes whatever directory you install things # into. # push @INC, "$ENV{HOME}/bin", "$ENV{HOME}/sh", ".", "sh"; use Socket; # Perl's TCP socket stuff # require "HTTPcon.pm"; # JC's HTTP connection routine [now included here] # #CONFIGURATION # We need a home directory and a place to put a few log messages: $home = $ENV{'HOME'} || '/u/guests/jc'; $logdir = "$home/log"; # # If you want this program to identify itself, this string will be # sent as the User-Agent: $myid = "~jc/sh/Send.pl"; # Will anyone notice this? # # This program has the ability to lie judiciously to the other end, # in order to satisfy some of the bizarre misimplementations of SMTP # that have been seen. This can be done by configuring any or all of # the following: # # Some hosts don't respond to SMTP connections, but another hostname # will work. Enter such hosts in this table, in lower case, to speed # up the job: %rehost = ( # 'hotmail.com' => 'mail.hotmail.com', # Are they fixed now? 'listserv.heanet.ie' => 'mail.heanet.ie', 'usa.net' => 'mxpool01.netaddress.usa.net', ); # # If we can't connect directly to a machine, we use this list of # hosts to use as relays. We connect to each in turn, and try to hand # off the message. @relay = ( # Mail relay hosts # 'ecf-guest.mit.edu', ); # # For each of the relay machines, we may need to send its mailer a # string to identify our machine. The default is our hostname, but # you can use this to specify other strings: %hostid = ( # Host names to send to specific hosts 'trillian.mit.edu' => 'ecf-guest.mit.edu', 'bigfoot.com' => 'eddie.mit.edu', 'yahoo.com' => 'ecf-guest.mit.edu', # 'yahoogroups.com' => 'trillian.mit.edu', 'mindspring.com' => 'ecf-guest.mit.edu', ); $hostid = 'trillian.mit.edu'; # For all other hosts # # You may also need to specify your user id differently for some # remote hosts. %userid = ( # User identification to send to hosts ); $userid = 'jc'; # For all other hosts # # You can also specify a full name to give in the From: lines: %fullnm = ( # Per-host names to send 'trillian.mit.edu' => 'John Chambers', 'bigfoot.com' => 'Jean Chambres', 'yahoo.com' => 'John Chambers', # 'yahoogroups.com' => 'Jean Chambres', 'mindspring.com' => 'Jean Chambres', ); $fullnm = "John Chambers"; # For all other hosts # # Some sites can't be handled this way, and you to use a different # email address depending on which recipient you are sending to. The # most common reason for this is mailing lists that only accept mail # from list members. %adrmap = ( # rcpt => sender mapping # 'tradtunes@yahoogroups.com' => 'John Chambers ', # 'scand@yahoogroups.com' => 'John Chambers ', 'rjelly@yahoogroups.com' => 'John Chambers ', 'gaybladesrapper@yahoogroups.com' => 'Jean Chambres ', # 'QueTrad@yahoogroups.com' => 'Jean Chambres ', ); # #ENVIRONMENT # We get our verbose level from the environment. If this program is # called "Send", then you might set V_Send to the verbose level. The # default is 1, which produces only serious messages. Higher numbers # will produce more output. # # We need to know about a couple of directories: $maildir = '/usr/mail'; $homedir = $ENV{HOME} || $maildir; # We will look for a 'list' subdirectory to either of these as a # place to find mailing-list files. # #EXAMPLES # #FILES # #BUGS # #KLUDGES # # We suppress Bcc: lines, after extracting the recipient info. This # was the easiest way of hiding these lines. We also hide any extra # To: header lines. The reason is that some mailers choke if they get # more than one To: line. We handle them properly, of course, but # strip them out so as not to offend sensitive email software. Maybe # they should be changed to Cc: lines? # #SEE ALSO # #AUTHOR # Copyright 1995, 1999, 2003 by John Chambers . # You are free to use this program as you wish, as long as you give # me credit (and take credit for your changes). If you come up with # any cool new features, please send me a copy. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # $| = 1; $" = ','; $exitstat = 0; # Set to nonzero for failures ($P = $0) =~ s".*/""; # Our program name, minus any directory &Vinit($ENV{"V_$P"}||2); # Our verbose level, defaults to 1 or 2 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Initialization: $HTTPalrm = 0; # Alarm needs to be handled $HTTPcons = 0; # Count of connections $HTTPcontime = time; # When the connection happens $HTTPtimeout = 30; # Timeout for connects # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Create our logfile: $logfile = "$logdir/$P.log"; # Where to write log messages mkdir($logdir,0755) unless -d $logdir; if (open(LOG,">>$logfile")) { ++$logopen; # Note that we have a log file } else { print STDERR "$P: Can't write \"$logfile\" ($!)\n"; } $argis = 'msg'; # How to interpret args $CRLF = "\r\n"; # RFC 822 line terminator $hlo = 'HELO'; # Default greeting $initCRLF = 0; # Whether to send initial blank line after connect $parsehdrs = 0; # Whether to read file headers #proto = getprotobyname('tcp'); $port = getservbyname('smtp','tcp'); $hdrs = 1; # Whether to generate email headers $sendfl = 1; # Send messages only if true $tryalt = 1; # Whether to try variants on host name $usehostcmd = 1; # Use host command to find mail hosts $usenslookupcmd = 1; # Use nslookup to file mail hosts chomp($hostid = `hostname`) unless $hostid; &vsend("$P: Our hostid=\"$hostid\"") if $V>2; chomp($userid = $ENV{LOGNAME} || $ENV{USER}) unless $userid; &vsend("$P: Our userid=\"$userid\"") if $V>2; chomp($fullnm = $ENV{FULLNAME}) unless $fullnm; &vsend("$P: Our fullnm=\"$fullnm\"") if $V>2; # Sort the command-line args into bins arg: for $arg (@ARGV) { if (($flg,$a) = ($arg =~ /^([-+])(.*)/)) { &vsend("$P: Option \"$a\"") if $V>3; if ($a =~ /^[DdVv]/) { push @opts, '-v'; &vsend("$P: opts=(@opts)") if $V>2; } elsif ($a =~ /^[Ff](.*)/) { $From = $1; &vsend("$P: From='$From'") if $V>2; } elsif ($a =~ /^[Tt](\d+)$/) { $HTTPtimeout = int($1); &vsend("$P: Timeout='$HTTPtimeout'") if $V>2; } elsif ($a =~ /^[Tt]/) { push @opts, '-t'; &vsend("$P: opts=(@opts)") if $V>2; } elsif ($a =~ /^[Ss](.*)/) { if ($a =~ /^send/i) { $sendfl = ($flg eq '+'); &vsend("$P: sendfl=\"$sendfl\"") if $V>2; } elsif ($a =~ /^sig/i) { $sigfl = ($flg eq '+'); &vsend("$P: sigfl=\"$sigfl\"") if $V>2; } else { $subj = $1; &vsend("$P: subj=\"$subj\"") if $V>2; } } else { &vsend("$P: Option \"$a\" not recognized, ignored.") if $V>2; } next arg; } &vsend("$P: Arg \"$arg\"") if $V>3; if ($arg eq 'to' ) {$argis = 'rcpt'; next arg} if ($arg eq 'list') {$argis = 'list'; next arg} if ($arg eq 'rcpt') {$argis = 'rcpt'; next arg} if ($arg eq 'file') {$argis = 'file'; $hdrs = 0; next arg} if ($argis eq 'rcpt') { # Recipients push @rcpts, $arg; &vsend("$P: rcpts: @rcpts") if $V>2; $parsehdrs = 0; next arg; } elsif ($argis eq 'list') { # Mailing lists push @lists, $arg; &vsend("$P: lists: @lists") if $V>2; $parsehdrs = 0; next arg; } else { # Anything else is file name push @files, $arg; &vsend("$P: files: @files") if $V>2; next arg; } } # If we got no file names, we read from standard input into a scratch # file, and send that message to all our recipients. unless (@files) { $tmpfil = "/tmp/Mail$$"; &vsend("$P: <= STDIN") if $V>1; # Needed if there are relic /tmp/Mail* files: # system 'rm /tmp/Mail*'; unless (open(TMP,">$tmpfil")) { &vsend("$P: Can't write \"$tmpfil\" ($!)") if $V>0; exit $!; } while ($l = ) {print TMP $l;} close TMP; push @files, $tmpfil; $parsehdrs = 0; } # If we got mailing list name(s), we look for the list files and # extract all the email addresses, adding them to @rcpts. list: for $l (@lists) { $listfil = ''; if ( -f ($listfil = "$homedir/mail/list/$l")) { } elsif ( -f ($listfil = "$maildir/list/$l")) { } else { next list } push @rcpts, &parselist($listfil); } unless (@rcpts) { $parsehdrs = 1; # Look for To: lines in files $opts[$#opts+1] = '-t'; # Sendmail flag with same meaning } file: for $f (@files) { &vsend("$P: Reading mail from $f ...") if $V>2; $from = ''; # May be set in headers unless (@rcpts) { # No global recipients &vsend("$P: Scan file \"$f\" for recipients ...") if $V>2; @rcpts = &parsehdrs($f); &vsend("$P: rcpts=(@rcpts)") if $V>2; $fromhdrs = 1; # Note recipients are from file } unless (@rcpts) { # Do we have any recipients? &vsend("$P: No recipients found in file \"$f\".") if $V>2; next file; } &vsend("$P: Send file \"$f\" to all recipients ...") if $V>2; for $r (@rcpts) { # Send out one file to all recipients &vsend("$P: Send file \"$f\" to \"$r\" ...") if $V>2; &fil($f,$r); } if ($fromhdrs) { # Did recipients come from file? @rcpts = (); # If so, forget about them $fromhdrs = 0; # No known recipients now } } if ($tmpfil) { unlink $tmpfil if $V<3; } exit $exitstat; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub HTTPalarm { # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # my $t = time - $HTTPcontime; print "\n" if $W3trace; # exit -1; $errmsg = 'timeout'; } sub HTTPcon { # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # NAME # # HTTPcon - make HTTP connection. # # # # SYNOPSIS # # $stat = &HTTPcon(*F,'fubar.com:1234'); # # # # DESCRIIPTION # # This accepts a URL's host:port portion, and attempts to make the # # connection. If successful, we return 1 with F open to the TCP # # socket. If we fail, we return 0, and F may or may not be open. # # (Maybe we should close it.) # # # # TIMEOUTS # # I've added a timeout kludge: If $HTTPtimeout is nonzero, we will # # exit after that many seconds. This is drastic, but it seems to be # # the only solution to the hung-connect problem. This is mostly # # used in webcat, which is used as a subprocess by other programs. # # If you call "webcat -T15 ...", it will exit after 15 seconds if # # the connection can't be made, and you can go about your business. # # # # AUTHOR # # John Chambers # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # local(*HTTPsock,$hp) = @_; local($a,$b,$c,$d); local(@addrs,$host,$port,$savsig,$t,$this,$that,$This,$That); $HTTPtimeout = 30 unless defined $HTTPtimeout; $W3trace = 0 unless defined $W3trace; if (($host,$port) = ($hp =~ m"^(.*):(\d+)$")) { print V "HTTPcon: host=\"$host\" port=\"$port\"\n" if $V>5; } else { $host = $hp; $port = 80; print V "HTTPcon: host=\"$host\" port=$port.\n" if $V>5; } $AF_INET = 2; $SOCK_STREAM = 1; $sockaddr = 'S n a4 x8'; ($name,$aliases,$proto) = getprotobyname('tcp'); ($name,$aliases,$port) = getservbyname($port,'tcp') unless $port =~ /^\d+$/; $thisaddr = "\0\0\0\0"; print "\n" if $W3trace; ($name,$aliases,$type,$len,@addrs) = gethostbyname($host); if (!@addrs) { $errmsg = "No address for \"$host\""; return 0; } $thataddr = $addrs[0]; $this = pack($sockaddr, $AF_INET, 0, $thisaddr); $that = pack($sockaddr, $AF_INET, $port, $thataddr); ($a,$b,$c,$d) = unpack('C4',$thisaddr); $This = "$a.$b.$c.$d:0"; ($a,$b,$c,$d) = unpack('C4',$thataddr); $That = "$a.$b.$c.$d:$port"; if (socket(HTTPsock,$AF_INET,$SOCK_STREAM,$proto)) { print V "HTTPcon: Got socket.\n" if $V>5; } else { print V "HTTPcon: Can't get socket ($!)\n" if $V>0; $exitstat = $!; return 0; } if (bind(HTTPsock,$this)) { $t = time - $HTTPcontime; print V "HTTPcon: Bind to \"$This\" succeeded in $t sec.\n" if $V>5; } else { $t = time - $HTTPcontime; print V "HTTPcon: Bind to \"$This\" failed in $t sec ($!)\n" if $V>2; $exitstat = $!; return 0; } ++$HTTPcons; $HTTPcontime = time; if ($HTTPtimeout > 0) { alarm $HTTPtimeout; $savsig = $SIG{ALRM}; $SIG{ALRM} = 'HTTPalarm'; print V "HTTPcon: Set alarm after $HTTPtimeout sec.\n" if $V>3; } print "\n" if $W3trace; print "HTTPcon: Connecting to \"$That\"\n" if $V>5; if (connect(HTTPsock,$that)) { $t = time - $HTTPcontime; print V "HTTPcon: Connect $HTTPcons to \"$That\" succeeded in $t sec.\n" if $V>5; print "\n" if $W3trace; if ($HTTPtimeout > 0) { alarm 0; $SIG{ALRM} = $savsig; print V "HTTPcon: Set alarm 0.\n" if $V>5; } } else { $t = time - $HTTPcontime; print "