# package Mail::Mailer; =head1 NAME Mail::Mailer - Simple interface to electronic mailing mechanisms =head1 SYNOPSIS use Mail::Mailer; use Mail::Mailer qw(mail); $mailer = new Mail::Mailer; $mailer = new Mail::Mailer $type, @args; $mailer->open(\%headers); print $mailer $body; $mailer->close; =head1 DESCRIPTION Sends mail using any of the built-in methods. You can alter the behaviour of a method by passing C<$command> to the C method. =over 4 =item C Use the C program to deliver the mail. C<$command> is the path to C. =item C Use the C protocol via Net::SMTP to deliver the mail. The server to use can be specified in C<@args> with $mailer = new Mail::Mailer 'smtp', Server => $server; The smtp mailer does not handle C and C lines, neither their C fellows. The C options enables debugging output from C. =item C Use qmail's qmail-inject program to deliver the mail. =item C Used for debugging, this displays the data on STDOUT. No mail is ever sent. C<$command> is ignored. =back C will search for executables in the above order. The default mailer will be the first one found. =head2 ARGUMENTS C can optionally be given a C<$command> and C<$type>. C<$type> is one C, C, ... given above. The meaning of C<$command> depends on C<$type>. C is given a reference to a hash. The hash consists of key and value pairs, the key being the name of the header field (eg, C), and the value being the corresponding contents of the header field. The value can either be a scalar (eg, C) or a reference to an array of scalars (C). =head1 TO DO Assist formatting of fields in ...::rfc822:send_headers to ensure valid in the face of newlines and longlines etc. Secure all forms of send_headers() against hacker attack and invalid contents. Especially "\n~..." in ...::mail::send_headers. =head1 ENVIRONMENT VARIABLES =over 4 =item PERL_MAILERS Augments/override the build in choice for binary used to send out our mail messages. Format: "type1:mailbinary1;mailbinary2;...:type2:mailbinaryX;...:..." Example: assume you want you use private sendmail binary instead of mailx, one could set C to: "mail:/does/not/exists:sendmail:$HOME/test/bin/sendmail" On systems which may include C<:> in file names, use C<|> as separator between type-groups. "mail:c:/does/not/exists|sendmail:$HOME/test/bin/sendmail" =back =head1 SEE ALSO Mail::Send =head1 AUTHORS Maintained by Mark Overmeer Original code written by Tim Bunce EFE, with a kick start from Graham Barr EFE. With contributions by Gerard Hickey EFE Small fix and documentation by Nathan Torkington EFE. =cut use Carp; use IO::Handle; use vars qw(@ISA $VERSION $MailerBinary $MailerType %Mailers @Mailers); use Config; use strict; $VERSION = "1.62"; sub Version { $VERSION } @ISA = qw(IO::Handle); # Suggested binaries for types? Should this be handled in the object class? @Mailers = ( # Headers-blank-Body all on stdin 'sendmail' => '/usr/lib/sendmail;/usr/sbin/sendmail;/usr/ucblib/sendmail', 'smtp' => undef, 'qmail' => '/usr/sbin/qmail-inject;/var/qmail/bin/qmail-inject', 'testfile' => undef ); if($ENV{PERL_MAILERS}) { push @Mailers , map { split /\:/, $_, 2} split /$Config{path_sep}/, $ENV{PERL_MAILERS}; } %Mailers = @Mailers; $MailerBinary = undef; # does this really need to be done? or should a default mailer be specfied? if($^O eq 'os2') { $Mailers{sendmail} = 'sendmail' unless is_exe($Mailers{sendmail}); } if($^O eq 'MacOS' || $^O eq 'VMS' || $^O eq 'MSWin32' || $^O eq 'os2') { $MailerType = 'smtp'; $MailerBinary = $Mailers{$MailerType}; } else { my $i; for($i = 0 ; $i < @Mailers ; $i += 2) { $MailerType = $Mailers[$i]; my $binary; if($binary = is_exe($Mailers{$MailerType})) { $MailerBinary = $binary; last; } } } sub import { shift; if(@_) { my $type = shift; my $exe = shift || $Mailers{$type}; carp "Cannot locate '$exe'" unless is_exe($exe); $MailerType = $type; $Mailers{$MailerType} = $exe; } } sub to_array { my($self, $thing) = @_; if (ref($thing)) { return @$thing; } else { return ($thing); } } sub is_exe { my $exe = shift || ''; my $cmd; foreach $cmd (split /\;/, $exe) { $cmd =~ s/^\s+//; # remove any options my $name = ($cmd =~ /^(\S+)/)[0]; # check for absolute or relative path return ($cmd) if (-x $name and ! -d $name and $name =~ m:[\\/]:); if (defined $ENV{PATH}) { my $dir; foreach $dir (split(/$Config{path_sep}/, $ENV{PATH})) { return "$dir/$cmd" if (-x "$dir/$name" && ! -d "$dir/$name"); } } } 0; } sub new { my($class, $type, @args) = @_; $type = $MailerType unless $type; croak "No MailerType specified" unless defined $type; my $exe = $Mailers{$type}; if(defined($exe)) { $exe = is_exe ($exe) if defined $type; $exe = $MailerBinary unless $exe; croak "No mailer type specified (and no default available), thus can not find executable program." unless $exe; } $class = "Mail::Mailer::$type"; eval "require $class" or die $@; my $glob = $class->SUPER::new; # local($glob) = gensym; # Make glob for FileHandle and attributes %{*$glob} = (Exe => $exe, Args => [ @args ] ); $glob; # bless $glob, $class; } sub open { my($self, $hdrs) = @_; my $exe = *$self->{Exe}; # || Carp::croak "$self->open: bad exe"; my $args = *$self->{Args}; _cleanup_hdrs($hdrs); my @to = $self->who_to($hdrs); $self->close; # just in case; # Fork and start a mailer (defined($exe) && open($self,"|-")) || $self->exec($exe, $args, \@to) || die $!; # Set the headers $self->set_headers($hdrs); # return self (a FileHandle) ready to accept the body $self; } sub _cleanup_hdrs { my $hdrs = shift; my $h; foreach $h (values %$hdrs) { foreach (ref($h) ? @{$h} : $h) { s/\n\s*/ /g; s/\s+$//; } } } sub exec { my($self, $exe, $args, $to) = @_; # Fork and exec the mailer (no shell involved to avoid risks) my @exe = split(/\s+/,$exe); exec(@exe, @$args, @$to); } sub can_cc { 1 } # overridden in subclass for mailer that can't sub who_to { my($self, $hdrs) = @_; my @to = $self->to_array($hdrs->{To}); if (!$self->can_cc) { # Can't cc/bcc so add them to @to push(@to, $self->to_array($hdrs->{Cc})) if $hdrs->{Cc}; push(@to, $self->to_array($hdrs->{Bcc})) if $hdrs->{Bcc}; } @to; } sub epilogue { # This could send a .signature, also see ::smtp subclass } sub close { my($self, @to) = @_; if (fileno($self)) { $self->epilogue; close($self) } } sub DESTROY { my $self = shift; $self->close; } 1;