File Coverage

blib/lib/Mail/Sendmail.pm
Criterion Covered Total %
statement 136 165 82.4
branch 35 82 42.6
condition 13 35 37.1
subroutine 10 11 90.9
pod 2 5 40.0
total 196 298 65.7


line stmt bran cond sub pod time code
1             package Mail::Sendmail;
2             # Mail::Sendmail by Milivoj Ivkovic
3             # see embedded POD documentation after __END__
4             # or http://alma.ch/perl/mail.html
5              
6             =head1 NAME
7              
8             Mail::Sendmail v. 0.79 - Simple platform independent mailer
9              
10             =cut
11              
12             $VERSION = '0.79';
13              
14             # *************** Configuration you may want to change *******************
15             # You probably want to set your SMTP server here (unless you specify it in
16             # every script), and leave the rest as is. See pod documentation for details
17              
18             %mailcfg = (
19             # List of SMTP servers:
20             'smtp' => [ qw( localhost ) ],
21             #'smtp' => [ qw( mail.mydomain.com ) ], # example
22              
23             'from' => '', # default sender e-mail, used when no From header in mail
24              
25             'mime' => 1, # use MIME encoding by default
26              
27             'retries' => 1, # number of retries on smtp connect failure
28             'delay' => 1, # delay in seconds between retries
29              
30             'tz' => '', # only to override automatic detection
31             'port' => 25, # change it if you always use a non-standard port
32             'debug' => 0 # prints stuff to STDERR
33             );
34              
35             # *******************************************************************
36              
37             require Exporter;
38 1     1   971 use strict;
  1         3  
  1         51  
39 1         154 use vars qw(
40             $VERSION
41             @ISA
42             @EXPORT
43             @EXPORT_OK
44             %mailcfg
45             $address_rx
46             $debug
47             $log
48             $error
49             $retry_delay
50             $connect_retries
51 1     1   6 );
  1         2  
52              
53 1     1   1237 use Socket;
  1         12519  
  1         3894  
54 1     1   4643 use Time::Local; # for automatic time zone detection
  1         4817  
  1         84  
55 1     1   1656 use Sys::Hostname; # for use of hostname in HELO
  1         3084  
  1         5470  
56              
57             # use MIME::QuotedPrint if available and configured in %mailcfg
58 1     1   1684 eval("use MIME::QuotedPrint");
  1         4277  
  1         49  
59             $mailcfg{'mime'} &&= (!$@);
60              
61             @ISA = qw(Exporter);
62             @EXPORT = qw(&sendmail);
63             @EXPORT_OK = qw(
64             %mailcfg
65             time_to_date
66             $address_rx
67             $debug
68             $log
69             $error
70             );
71              
72             # regex for e-mail addresses where full=$1, user=$2, domain=$3
73             # see pod documentation about this regex
74              
75             my $word_rx = '[\x21\x23-\x27\x2A-\x2B\x2D\x2F\w\x3D\x3F]+';
76             my $user_rx = $word_rx # valid chars
77             .'(?:\.' . $word_rx . ')*' # possibly more words preceded by a dot
78             ;
79             my $dom_rx = '\w[-\w]*(?:\.\w[-\w]*)*'; # less valid chars in domain names
80             my $ip_rx = '\[\d{1,3}(?:\.\d{1,3}){3}\]';
81              
82             $address_rx = '((' . $user_rx . ')\@(' . $dom_rx . '|' . $ip_rx . '))';
83             ; # v. 0.61
84              
85             sub time_to_date {
86             # convert a time() value to a date-time string according to RFC 822
87              
88 2   33 2 1 123 my $time = $_[0] || time(); # default to now if no argument
89              
90 2         13 my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
91 2         6 my @wdays = qw(Sun Mon Tue Wed Thu Fri Sat);
92              
93 2         64 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
94             = localtime($time);
95              
96 2         6 my $TZ = $mailcfg{'tz'};
97 2 50       7 if ( $TZ eq "" ) {
98             # offset in hours
99 2         39 my $offset = sprintf "%.1f", (timegm(localtime) - time) / 3600;
100 2         94 my $minutes = sprintf "%02d", abs( $offset - int($offset) ) * 60;
101 2         6 $TZ = sprintf("%+03d", int($offset)) . $minutes;
102             }
103 2         19 return join(" ",
104             ($wdays[$wday] . ','),
105             $mday,
106             $months[$mon],
107             $year+1900,
108             sprintf("%02d:%02d:%02d", $hour, $min, $sec),
109             $TZ
110             );
111             } # end sub time_to_date
112              
113             sub sendmail {
114              
115 1     1 1 14 $error = '';
116 1         23 $log = "Mail::Sendmail v. $VERSION - " . scalar(localtime()) . "\n";
117              
118 1         2 my $CRLF = "\015\012";
119 1         5 local $/ = $CRLF;
120 1         4 local $\ = ''; # to protect us from outside settings
121 1         2 local $_;
122              
123 1         2 my (%mail, $k,
124             $smtp, $server, $port, $connected, $localhost,
125             $fromaddr, $recip, @recipients, $to, $header,
126             );
127              
128             # -------- a few internal subs ----------
129             sub fail {
130             # things to do before returning a sendmail failure
131 0 0   0 0 0 print STDERR @_ if $^W;
132 0         0 $error .= join(" ", @_) . "\n";
133 0         0 close S;
134 0         0 return 0;
135             }
136              
137             sub socket_write {
138 13     13 0 25 my $i;
139 13         40 for $i (0..$#_) {
140             # accept references, so we don't copy potentially big data
141 15 100       67 my $data = ref($_[$i]) ? $_[$i] : \$_[$i];
142 15 50       43 if ($mailcfg{'debug'} > 5) {
143 0 0       0 if (length($$data) < 500) {
144 0         0 print ">", $$data;
145             }
146             else {
147 0         0 print "> [...", length($$data), " bytes sent ...]\n";
148             }
149             }
150 15 50       753 print(S $$data) || return 0;
151             }
152 13         78 1;
153             }
154              
155             sub socket_read {
156 7     7 0 14 my $response; # for multi-line server responses
157 7         10 do {
158 7         280487 chomp($_ = );
159 7 50       75 print "<$_\n" if $mailcfg{'debug'} > 5;
160 7 50 33     108 if (/^[45]/ or !$_) {
161 0         0 return; # return false
162             }
163 7         104 $response .= $_;
164             } while (/^[\d]+-/);
165 7         67 return $response;
166             }
167             # -------- end of internal subs ----------
168              
169             # all config keys to lowercase, to prevent typo errors
170 1         7 foreach $k (keys %mailcfg) {
171 8 50       21 if ($k =~ /[A-Z]/) {
172 0         0 $mailcfg{lc($k)} = $mailcfg{$k};
173             }
174             }
175              
176             # redo mail hash, arranging keys case etc...
177 1         5 while (@_) {
178 5         8 $k = shift @_;
179 5 0 33     11 if (!$k and $^W) {
180 0         0 warn "Received false mail hash key: \'$k\'. Did you forget to put it in quotes?\n";
181             }
182              
183             # arrange keys case
184 5         9 $k = ucfirst lc($k);
185              
186 5         8 $k =~ s/\s*:\s*$//o; # kill colon (and possible spaces) at end, we add it later.
187             # uppercase also after "-", so people don't complain that headers case is different
188             # than in Outlook.
189 5         6 $k =~ s/-(.)/"-" . uc($1)/ge;
  0         0  
190 5         25 $mail{$k} = shift @_;
191             }
192              
193 1   33     5 $smtp = $mail{'Smtp'} || $mail{'Server'};
194 1 50 33     11 unshift @{$mailcfg{'smtp'}}, $smtp if ($smtp and $mailcfg{'smtp'}->[0] ne $smtp);
  1         4  
195              
196             # delete non-header keys, so we don't send them later as mail headers
197             # I like this syntax, but it doesn't seem to work with AS port 5.003_07:
198             # delete @mail{'Smtp', 'Server'};
199             # so instead:
200 1         3 delete $mail{'Smtp'}; delete $mail{'Server'};
  1         2  
201              
202 1   50     7 $mailcfg{'port'} = $mail{'Port'} || $mailcfg{'port'} || 25;
203 1         2 delete $mail{'Port'};
204              
205             { # don't warn for undefined values below
206 1         2 local $^W = 0;
  1         4  
207 1         5 $mail{'Message'} = join("", $mail{'Message'}, $mail{'Body'}, $mail{'Text'});
208             }
209              
210             # delete @mail{'Body', 'Text'};
211 1         3 delete $mail{'Body'}; delete $mail{'Text'};
  1         1  
212              
213             # Extract 'From:' e-mail address to use as envelope sender
214              
215 1   33     29 $fromaddr = $mail{'Sender'} || $mail{'From'} || $mailcfg{'from'};
216 1         3 delete $mail{'Sender'};
217 1 50       257 unless ($fromaddr =~ /$address_rx/) {
218 0         0 return fail("Bad or missing From address: \'$fromaddr\'");
219             }
220 1         5 $fromaddr = $1;
221              
222             # add Date header if needed
223 1   33     6 $mail{Date} ||= time_to_date() ;
224 1         6 $log .= "Date: $mail{Date}\n";
225              
226             # cleanup message, and encode if needed
227 1         3 $mail{'Message'} =~ s/\r\n/\n/go; # normalize line endings, step 1 of 2 (next step after MIME encoding)
228              
229 1   50     7 $mail{'Mime-Version'} ||= '1.0';
230 1   50     6 $mail{'Content-Type'} ||= 'text/plain; charset="iso-8859-1"';
231              
232 1 50 33     10 unless ( $mail{'Content-Transfer-Encoding'}
233             || $mail{'Content-Type'} =~ /multipart/io )
234             {
235 1 50       5 if ($mailcfg{'mime'}) {
236 1         4 $mail{'Content-Transfer-Encoding'} = 'quoted-printable';
237 1         14 $mail{'Message'} = encode_qp($mail{'Message'});
238             }
239             else {
240 0         0 $mail{'Content-Transfer-Encoding'} = '8bit';
241 0 0       0 if ($mail{'Message'} =~ /[\x80-\xFF]/o) {
242 0         0 $error .= "MIME::QuotedPrint not present!\nSending 8bit characters, hoping it will come across OK.\n";
243 0 0       0 warn "MIME::QuotedPrint not present!\n",
244             "Sending 8bit characters without encoding, hoping it will come across OK.\n"
245             if $^W;
246             }
247             }
248             }
249              
250 1         3 $mail{'Message'} =~ s/^\./\.\./gom; # handle . as first character
251 1         5 $mail{'Message'} =~ s/\n/$CRLF/go; # normalize line endings, step 2.
252              
253             # Get recipients
254             { # don't warn for undefined values below
255 1         2 local $^W = 0;
  1         3  
256 1         5 $recip = join(", ", $mail{To}, $mail{Cc}, $mail{Bcc});
257             }
258              
259 1         2 delete $mail{'Bcc'};
260              
261 1         2 @recipients = ();
262 1         169 while ($recip =~ /$address_rx/go) {
263 1         6 push @recipients, $1;
264             }
265 1 50       5 unless (@recipients) {
266 0         0 return fail("No recipient!")
267             }
268              
269             # get local hostname for polite HELO
270 1   50     6 $localhost = hostname() || 'localhost';
271              
272 1         16 foreach $server ( @{$mailcfg{'smtp'}} ) {
  1         3  
273             # open socket needs to be inside this foreach loop on Linux,
274             # otherwise all servers fail if 1st one fails !??! why?
275 1 50       937 unless ( socket S, AF_INET, SOCK_STREAM, scalar(getprotobyname 'tcp') ) {
276 0         0 return fail("socket failed ($!)")
277             }
278              
279 1 50       9 print "- trying $server\n" if $mailcfg{'debug'} > 1;
280              
281 1         4 $server =~ s/\s+//go; # remove spaces just in case of a typo
282             # extract port if server name like "mail.domain.com:2525"
283 1 50       6 $port = ($server =~ s/:(\d+)$//o) ? $1 : $mailcfg{'port'};
284 1         3 $smtp = $server; # save $server for use outside foreach loop
285              
286 1         1857 my $smtpaddr = inet_aton $server;
287 1 50       9 unless ($smtpaddr) {
288 0         0 $error .= "$server not found\n";
289 0         0 next; # next server
290             }
291              
292 1         4 my $retried = 0; # reset retries for each server
293 1   33     27561 while ( ( not $connected = connect S, pack_sockaddr_in($port, $smtpaddr) )
294             and ( $retried < $mailcfg{'retries'} )
295             ) {
296 0         0 $retried++;
297 0         0 $error .= "connect to $server failed ($!)\n";
298 0 0       0 print "- connect to $server failed ($!)\n" if $mailcfg{'debug'} > 1;
299 0 0       0 print "retrying in $mailcfg{'delay'} seconds...\n" if $mailcfg{'debug'} > 1;
300 0         0 sleep $mailcfg{'delay'};
301             }
302              
303 1 50       23 if ( $connected ) {
304 1 50       7 print "- connected to $server\n" if $mailcfg{'debug'} > 3;
305 1         5 last;
306             }
307             else {
308 0         0 $error .= "connect to $server failed\n";
309 0 0       0 print "- connect to $server failed, next server...\n" if $mailcfg{'debug'} > 1;
310 0         0 next; # next server
311             }
312             }
313              
314 1 50       5 unless ( $connected ) {
315 0         0 return fail("connect to $smtp failed ($!) no (more) retries!")
316             };
317              
318             {
319 1         2 local $^W = 0; # don't warn on undefined variables
  1         9  
320             # Add info to log variable
321 1         13 $log .= "Server: $smtp Port: $port\n"
322             . "From: $fromaddr\n"
323             . "Subject: $mail{Subject}\n"
324             . "To: ";
325             }
326              
327 1         7 my($oldfh) = select(S); $| = 1; select($oldfh);
  1         13  
  1         7  
328              
329 1 50       6 socket_read()
330             || return fail("Connection error from $smtp on port $port ($_)");
331 1 50       10 socket_write("HELO $localhost$CRLF")
332             || return fail("send HELO error");
333 1 50       4 socket_read()
334             || return fail("HELO error ($_)");
335 1 50       21 socket_write("MAIL FROM: <$fromaddr>$CRLF")
336             || return fail("send MAIL FROM: error");
337 1 50       6 socket_read()
338             || return fail("MAIL FROM: error ($_)");
339              
340 1         7 foreach $to (@recipients) {
341 1 50       10 socket_write("RCPT TO: <$to>$CRLF")
342             || return fail("send RCPT TO: error");
343 1 50       5 socket_read()
344             || return fail("RCPT TO: error ($_)");
345 1         9 $log .= "$to\n ";
346             }
347              
348             # start data part
349              
350 1 50       9 socket_write("DATA$CRLF")
351             || return fail("send DATA error");
352 1 50       9 socket_read()
353             || return fail("DATA error ($_)");
354              
355             # print headers
356 1         16 foreach $header (keys %mail) {
357 8 100       23 next if $header eq "Message";
358 7         44 $mail{$header} =~ s/\s+$//o; # kill possible trailing garbage
359 7 50       29 socket_write("$header: $mail{$header}$CRLF")
360             || return fail("send $header: error");
361             };
362              
363             #- test diconnecting from network here, to see what happens
364             #- print STDERR "DISCONNECT NOW!\n";
365             #- sleep 4;
366             #- print STDERR "trying to continue, expecting an error... \n";
367              
368             # send message body (passed as a reference, in case it's big)
369 1 50       7 socket_write($CRLF, \$mail{'Message'}, "$CRLF.$CRLF")
370             || return fail("send message error");
371 1 50       7 socket_read()
372             || return fail("message transmission error ($_)");
373 1         11 $log .= "\nResult: $_";
374              
375             # finish
376 1 50       9 socket_write("QUIT$CRLF")
377             || return fail("send QUIT error");
378 1         5 socket_read();
379 1         134 close S;
380              
381 1         29 return 1;
382             } # end sub sendmail
383              
384             1;
385             __END__