forked from github/Quit.mwForum
385 lines
11 KiB
Perl
385 lines
11 KiB
Perl
# Based on Mail::Sendmail by Milivoj Ivkovic <mi@alma.ch>
|
|
# This version has been slightly modified for mwForum.
|
|
# The original version can be obtained from CPAN.
|
|
|
|
package TyfSendmail;
|
|
$VERSION = "1.15.0";
|
|
|
|
# *************** Configuration you may want to change *******************
|
|
# You probably want to set your SMTP server here (unless you specify it in
|
|
# every script), and leave the rest as is. See pod documentation for details
|
|
|
|
%mailcfg = (
|
|
|
|
# List of SMTP servers:
|
|
'smtp' => [],
|
|
|
|
'from' => '', # default sender e-mail, used when no From header in mail
|
|
|
|
'mime' => 1, # use MIME encoding by default
|
|
|
|
'retries' => 1, # number of retries on smtp connect failure
|
|
'delay' => 1, # delay in seconds between retries
|
|
|
|
'tz' => '', # only to override automatic detection
|
|
'port' => 25, # change it if you always use a non-standard port
|
|
'debug' => 0 # prints stuff to STDERR
|
|
);
|
|
|
|
# *******************************************************************
|
|
|
|
use strict;
|
|
require Exporter;
|
|
use vars qw(
|
|
$VERSION
|
|
@ISA
|
|
@EXPORT
|
|
@EXPORT_OK
|
|
%mailcfg
|
|
$default_smtp_server
|
|
$default_smtp_port
|
|
$default_sender
|
|
$TZ
|
|
$use_MIME
|
|
$address_rx
|
|
$debug
|
|
$log
|
|
$error
|
|
$retry_delay
|
|
$connect_retries
|
|
);
|
|
|
|
use Socket;
|
|
use Time::Local; # for automatic time zone detection
|
|
|
|
# use MIME::QuotedPrint if available and configured in %mailcfg
|
|
eval("use MIME::QuotedPrint");
|
|
$mailcfg{mime} &&= ( !$@ );
|
|
|
|
@ISA = qw(Exporter);
|
|
@EXPORT = qw(&sendmail);
|
|
@EXPORT_OK = qw(
|
|
%mailcfg
|
|
time_to_date
|
|
$default_smtp_server
|
|
$default_smtp_port
|
|
$default_sender
|
|
$TZ
|
|
$address_rx
|
|
$debug
|
|
$log
|
|
$error
|
|
);
|
|
|
|
# regex for e-mail addresses where full=$1, user=$2, domain=$3
|
|
# see pod documentation about this regex
|
|
|
|
my $word_rx = '[\x21\x23-\x27\x2A-\x2B\x2D\w\x3D\x3F]+';
|
|
my $user_rx = $word_rx # valid chars
|
|
. '(?:\.' . $word_rx . ')*' # possibly more words preceded by a dot
|
|
;
|
|
my $dom_rx = '\w[-\w]+(?:\.\w[-\w]+)*'; # less valid chars in domain names
|
|
my $ip_rx = '\[\d{1,3}(?:\.\d{1,3}){3}\]';
|
|
|
|
$address_rx = '\b((' . $user_rx . ')\@(' . $dom_rx . '\b|' . $ip_rx . '))';
|
|
; # v. 0.4
|
|
|
|
sub time_to_date {
|
|
|
|
# convert a time() value to a date-time string according to RFC 822
|
|
|
|
my $time = $_[0] || time(); # default to now if no argument
|
|
|
|
my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
|
|
my @wdays = qw(Sun Mon Tue Wed Thu Fri Sat);
|
|
|
|
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
|
|
localtime($time);
|
|
|
|
$TZ ||= $mailcfg{tz};
|
|
|
|
if ( $TZ eq "" ) {
|
|
|
|
# offset in hours
|
|
my $offset = sprintf "%.1f", ( timegm(localtime) - time ) / 3600;
|
|
my $minutes = sprintf "%02d", ( $offset - int($offset) ) * 60;
|
|
$TZ = sprintf( "%+03d", int($offset) ) . $minutes;
|
|
}
|
|
return join( " ",
|
|
( $wdays[$wday] . ',' ),
|
|
$mday, $months[$mon],
|
|
$year + 1900,
|
|
sprintf( "%02d", $hour ) . ":" . sprintf( "%02d", $min ), $TZ );
|
|
} # end sub time_to_date
|
|
|
|
sub sendmail {
|
|
|
|
# original sendmail 1.21 by Christian Mallwitz.
|
|
# Modified and 'modulized' by mi@alma.ch
|
|
|
|
$error = '';
|
|
$log = "TyfSendmail v. $VERSION - " . scalar( localtime() ) . "\n";
|
|
|
|
local $_;
|
|
local $/ = "\015\012";
|
|
|
|
my (
|
|
%mail, $k, $smtp, $server, $port,
|
|
$connected, $localhost, $message, $fromaddr, $recip,
|
|
@recipients, $to, $header,
|
|
);
|
|
|
|
sub fail {
|
|
|
|
# things to do before returning a sendmail failure
|
|
#print STDERR @_ if $^W;
|
|
$error .= join( " ", @_ ) . "\n";
|
|
close S;
|
|
return 0;
|
|
}
|
|
|
|
# all config keys to lowercase, to prevent typo errors
|
|
foreach $k ( keys %mailcfg ) {
|
|
if ( $k =~ /[A-Z]/ ) {
|
|
$mailcfg{ lc($k) } = $mailcfg{$k};
|
|
}
|
|
}
|
|
|
|
# redo hash, arranging keys case etc...
|
|
while (@_) {
|
|
|
|
# arrange keys case
|
|
$k = ucfirst lc( shift @_ );
|
|
|
|
if ( !$k and $^W ) {
|
|
warn
|
|
"Received false mail hash key: \'$k\'. Did you forget to put it in quotes?\n";
|
|
}
|
|
|
|
$k =~ s/\s*:\s*$//o
|
|
; # kill colon (and possible spaces) at end, we add it later.
|
|
$mail{$k} = shift @_;
|
|
}
|
|
|
|
$smtp = $mail{Smtp} || $mail{Server} || $default_smtp_server;
|
|
unshift @{ $mailcfg{smtp} }, $smtp
|
|
if ( $smtp and $mailcfg{smtp}->[0] ne $smtp );
|
|
|
|
# delete non-header keys, so we don't send them later as mail headers
|
|
# I like this syntax, but it doesn't seem to work with AS port 5.003_07:
|
|
# delete @mail{'Smtp', 'Server'};
|
|
# so instead:
|
|
delete $mail{Smtp};
|
|
delete $mail{Server};
|
|
|
|
$mailcfg{port} = $mail{Port} || $default_smtp_port || $mailcfg{port} || 25;
|
|
delete $mail{Port};
|
|
|
|
# for backward compatibility only
|
|
$mailcfg{retries} = $connect_retries if defined($connect_retries);
|
|
$mailcfg{delay} = $retry_delay if defined($retry_delay);
|
|
|
|
{ # don't warn for undefined values below
|
|
local $^W = 0;
|
|
$message = join( "", $mail{Message}, $mail{Body}, $mail{Text} );
|
|
}
|
|
|
|
# delete @mail{'Message', 'Body', 'Text'};
|
|
delete $mail{Message};
|
|
delete $mail{Body};
|
|
delete $mail{Text};
|
|
|
|
# Extract 'From:' e-mail address
|
|
|
|
$fromaddr = $mail{From} || $default_sender || $mailcfg{from};
|
|
unless ( $fromaddr =~ /$address_rx/ ) {
|
|
return fail("Bad or missing From address: \'$fromaddr\'");
|
|
}
|
|
$fromaddr = $1;
|
|
|
|
# add Date header if needed
|
|
$mail{Date} ||= time_to_date();
|
|
$log .= "Date: $mail{Date}\n";
|
|
|
|
# cleanup message, and encode if needed
|
|
$message =~ s/^\./\.\./gom; # handle . as first character
|
|
$message =~ s/\r\n/\n/go
|
|
; # normalize line endings, step 1 of 2 (next step after MIME encoding)
|
|
|
|
$mail{'MIME-version'} ||= '1.0';
|
|
$mail{'Content-type'} ||= "text/plain; charset=\"iso-8859-1\"";
|
|
|
|
unless ( $mail{'Content-transfer-encoding'}
|
|
|| $mail{'Content-type'} =~ /multipart/io )
|
|
{
|
|
if ( $mailcfg{mime} ) {
|
|
$mail{'Content-transfer-encoding'} = 'quoted-printable';
|
|
$message = encode_qp($message);
|
|
}
|
|
else {
|
|
$mail{'Content-transfer-encoding'} = '8bit';
|
|
if ( $message =~ /[\x80-\xFF]/o ) {
|
|
$error .=
|
|
"MIME::QuotedPrint not present!\nSending 8bit characters, hoping it will come across OK.\n";
|
|
warn "MIME::QuotedPrint not present!\n",
|
|
"Sending 8bit characters, hoping it will come across OK.\n"
|
|
if $^W;
|
|
}
|
|
}
|
|
}
|
|
|
|
$message =~ s/\n/\015\012/go; # normalize line endings, step 2.
|
|
|
|
# Get recipients
|
|
{ # don't warn for undefined values below
|
|
local $^W = 0;
|
|
$recip = join( ", ", $mail{To}, $mail{Cc}, $mail{Bcc} );
|
|
}
|
|
|
|
delete $mail{Bcc};
|
|
|
|
@recipients = ();
|
|
while ( $recip =~ /$address_rx/go ) {
|
|
push @recipients, $1;
|
|
}
|
|
unless (@recipients) {
|
|
return fail("No recipient!");
|
|
}
|
|
|
|
# get local hostname for polite HELO
|
|
$localhost = ( gethostbyname('localhost') )[0] || 'localhost';
|
|
|
|
foreach $server ( @{ $mailcfg{smtp} } ) {
|
|
|
|
# open socket needs to be inside this foreach loop on Linux,
|
|
# otherwise all servers fail if 1st one fails !??! why?
|
|
unless ( socket S, AF_INET, SOCK_STREAM, ( getprotobyname 'tcp' )[2] ) {
|
|
return fail("socket failed ($!)");
|
|
}
|
|
|
|
#print "- trying $server\n" if $mailcfg{debug} > 1;
|
|
|
|
$server =~ s/\s+//go; # remove spaces just in case of a typo
|
|
# extract port if server name like "mail.domain.com:2525"
|
|
( $server =~ s/:(.+)$//o ) ? $port = $1 : $port = $mailcfg{port};
|
|
$smtp = $server; # save $server for use outside foreach loop
|
|
|
|
my $smtpaddr = inet_aton $server;
|
|
unless ($smtpaddr) {
|
|
$error .= "$server not found\n";
|
|
next; # next server
|
|
}
|
|
|
|
my $retried = 0; # reset retries for each server
|
|
while (
|
|
(
|
|
not $connected = connect S, pack_sockaddr_in( $port, $smtpaddr )
|
|
)
|
|
and ( $retried < $mailcfg{retries} )
|
|
)
|
|
{
|
|
$retried++;
|
|
$error .= "connect to $server failed ($!)\n";
|
|
|
|
#print "- connect to $server failed ($!)\n" if $mailcfg{debug} > 1;
|
|
#print "retrying in $mailcfg{delay} seconds...\n" if $mailcfg{debug} > 1;
|
|
sleep $mailcfg{delay};
|
|
}
|
|
|
|
if ($connected) {
|
|
|
|
#print "- connected to $server\n" if $mailcfg{debug} > 3;
|
|
last;
|
|
}
|
|
else {
|
|
$error .= "connect to $server failed\n";
|
|
|
|
#print "- connect to $server failed, next server...\n" if $mailcfg{debug} > 1;
|
|
next; # next server
|
|
}
|
|
}
|
|
|
|
unless ($connected) {
|
|
return fail("connect to $smtp failed ($!) no (more) retries!");
|
|
}
|
|
|
|
{
|
|
local $^W = 0; # don't warn on undefined variables
|
|
# Add info to log variable
|
|
$log .=
|
|
"Server: $smtp Port: $port\n"
|
|
. "From: $fromaddr\n"
|
|
. "Subject: $mail{Subject}\n" . "To: ";
|
|
}
|
|
|
|
my ($oldfh) = select(S);
|
|
$| = 1;
|
|
select($oldfh);
|
|
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
return fail("Connection error from $smtp on port $port ($_)");
|
|
}
|
|
|
|
print S "HELO $localhost\015\012";
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
return fail("HELO error ($_)");
|
|
}
|
|
|
|
print S "mail from: <$fromaddr>\015\012";
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
return fail("mail From: error ($_)");
|
|
}
|
|
|
|
foreach $to (@recipients) {
|
|
|
|
#if ($debug) { print STDERR "sending to: <$to>\n"; }
|
|
print S "rcpt to: <$to>\015\012";
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
$log .= "!Failed: $to\n ";
|
|
return fail("Error sending to <$to> ($_)\n");
|
|
}
|
|
else {
|
|
$log .= "$to\n ";
|
|
}
|
|
}
|
|
|
|
# start data part
|
|
print S "data\015\012";
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
return fail("Cannot send data ($_)");
|
|
}
|
|
|
|
# print headers
|
|
foreach $header ( keys %mail ) {
|
|
$mail{$header} =~ s/\s+$//o; # kill possible trailing garbage
|
|
print S "$header: ", $mail{$header}, "\015\012";
|
|
}
|
|
|
|
#- test diconnecting from network here, to see what happens
|
|
#- print STDERR "DISCONNECT NOW!\n";
|
|
#- sleep 4;
|
|
#- print STDERR "trying to continue, expecting an error... \n";
|
|
|
|
# send message body
|
|
print S "\015\012", $message, "\015\012.\015\012";
|
|
|
|
chomp( $_ = <S> );
|
|
if ( /^[45]/ or !$_ ) {
|
|
return fail("message transmission failed ($_)");
|
|
}
|
|
|
|
# finish
|
|
print S "quit\015\012";
|
|
$_ = <S>;
|
|
close S;
|
|
|
|
return 1;
|
|
} # end sub sendmail
|
|
|
|
1;
|