File Coverage

blib/lib/Mail/Sendmail.pm
Criterion Covered Total %
statement 164 285 57.5
branch 46 182 25.2
condition 15 48 31.2
subroutine 13 19 68.4
pod 2 6 33.3
total 240 540 44.4


line stmt bran cond sub pod time code
1             package Mail::Sendmail;
2              
3             # Mail::Sendmail by Milivoj Ivkovic
4             # see embedded POD documentation after __END__
5             # or http://alma.ch/perl/mail.html
6              
7             =head1 NAME
8              
9             Mail::Sendmail - Simple platform independent mailer
10              
11             =cut
12              
13             require 5.006;
14              
15             our $VERSION = "0.80";
16              
17 1     1   6575 use strict;
  1         3  
  1         41  
18 1     1   11 use warnings;
  1         2  
  1         67  
19              
20 1     1   523 use parent 'Exporter';
  1         371  
  1         7  
21              
22             # *************** Configuration you may want to change *******************
23             # You probably want to set your SMTP server here (unless you specify it in
24             # every script), and leave the rest as is. See pod documentation for details
25              
26             our %mailcfg = (
27             # List of SMTP servers:
28             'smtp' => [ qw( localhost ) ],
29             #'smtp' => [ qw( mail.mydomain.com ) ], # example
30              
31             'from' => '', # default sender e-mail, used when no From header in mail
32              
33             'mime' => 1, # use MIME encoding by default
34              
35             'retries' => 1, # number of retries on smtp connect failure
36             'delay' => 1, # delay in seconds between retries
37              
38             'tz' => '', # only to override automatic detection
39             'port' => 25, # change it if you always use a non-standard port
40             'debug' => 0 # prints stuff to STDERR
41             );
42              
43             # *******************************************************************
44              
45             our $address_rx;
46             our $debug;
47             our $log;
48             our $error;
49             our $retry_delay;
50             our $connect_retries;
51             our $auth_support;
52              
53 1     1   920 use Socket;
  1         5484  
  1         634  
54 1     1   682 use Time::Local; # for automatic time zone detection
  1         2313  
  1         77  
55 1     1   634 use Sys::Hostname; # for use of hostname in HELO
  1         1508  
  1         69  
56 1     1   608 use Sys::Hostname::Long; # for use of hostname in HELO
  1         1590  
  1         1524  
57              
58             #use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex);
59              
60             $auth_support = 'DIGEST-MD5 CRAM-MD5 PLAIN LOGIN';
61              
62             # use MIME::QuotedPrint if available and configured in %mailcfg
63 1     1   824 eval("use MIME::QuotedPrint");
  1         2098  
  1         51  
64             $mailcfg{'mime'} &&= (!$@);
65              
66             our @EXPORT = qw(&sendmail);
67             our @EXPORT_OK = qw(
68             %mailcfg
69             time_to_date
70             $address_rx
71             $debug
72             $log
73             $error
74             );
75              
76             # regex for e-mail addresses where full=$1, user=$2, domain=$3
77             # see pod documentation about this regex
78              
79             my $word_rx = '[\x21\x23-\x27\x2A-\x2B\x2D\x2F\w\x3D\x3F]+';
80             my $user_rx = $word_rx # valid chars
81             .'(?:\.' . $word_rx . ')*' # possibly more words preceded by a dot
82             ;
83             my $dom_rx = '\w[-\w]*(?:\.\w[-\w]*)*'; # less valid chars in domain names
84             my $ip_rx = '\[\d{1,3}(?:\.\d{1,3}){3}\]';
85              
86             $address_rx = '((' . $user_rx . ')\@(' . $dom_rx . '|' . $ip_rx . '))';
87             ; # v. 0.61
88              
89             sub _require_md5 {
90 0     0   0 eval { require Digest::MD5; Digest::MD5->import(qw(md5 md5_hex)); };
  0         0  
  0         0  
91 0 0       0 $error .= $@ if $@;
92 0 0       0 return ($@ ? undef : 1);
93             }
94              
95             sub _require_base64 {
96 0     0   0 eval {
97 0         0 require MIME::Base64; MIME::Base64->import(qw(encode_base64 decode_base64));
  0         0  
98             };
99 0 0       0 $error .= $@ if $@;
100 0 0       0 return ($@ ? undef : 1);
101             }
102              
103             sub _hmac_md5 {
104 0     0   0 my ($pass, $ckey) = @_;
105 0         0 my $size = 64;
106 0 0       0 $pass = md5($pass) if length($pass) > $size;
107 0         0 my $ipad = $pass ^ (chr(0x36) x $size);
108 0         0 my $opad = $pass ^ (chr(0x5c) x $size);
109 0         0 return md5_hex($opad, md5($ipad, $ckey));
110             }
111              
112             sub _digest_md5 {
113 0     0   0 my ($user, $pass, $challenge, $realm) = @_;
114              
115 0         0 my %ckey = map { /^([^=]+)="?(.+?)"?$/ } split(/,/, $challenge);
  0         0  
116 0   0     0 $realm ||= $ckey{realm}; #($user =~ s/\@(.+)$//o) ? $1 : $server;
117 0         0 my $nonce = $ckey{nonce};
118 0         0 my $cnonce = &make_cnonce;
119 0   0     0 my $uri = join('/', 'smtp', hostname()||'localhost', $ckey{realm});
120 0         0 my $qop = 'auth';
121 0         0 my $nc = '00000001';
122 0         0 my($hv, $a1, $a2);
123 0         0 $hv = md5("$user:$realm:$pass");
124 0         0 $a1 = md5_hex("$hv:$nonce:$cnonce");
125 0         0 $a2 = md5_hex("AUTHENTICATE:$uri");
126 0         0 $hv = md5_hex("$a1:$nonce:$nc:$cnonce:$qop:$a2");
127 0         0 return qq(username="$user",realm="$ckey{realm}",nonce="$nonce",nc=$nc,cnonce="$cnonce",digest-uri="$uri",response=$hv,qop=$qop);
128             }
129              
130             sub make_cnonce {
131 0     0 0 0 my $s = '' ;
132 0         0 for(1..16) { $s .= chr(rand 256) }
  0         0  
133 0         0 $s = encode_base64($s, "");
134 0         0 $s =~ s/\W/X/go;
135 0         0 return substr($s, 0, 16);
136             }
137              
138             sub time_to_date {
139             # convert a time() value to a date-time string according to RFC 822
140              
141 2   33 2 1 213808 my $time = $_[0] || time(); # default to now if no argument
142              
143 2         30 my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
144 2         7 my @wdays = qw(Sun Mon Tue Wed Thu Fri Sat);
145              
146 2         68 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
147             = localtime($time);
148              
149 2         9 my $TZ = $mailcfg{'tz'};
150 2 50       9 if ( $TZ eq "" ) {
151             # offset in hours
152 2         30 my $offset = sprintf "%.1f", (timegm(localtime) - time) / 3600;
153 2         123 my $minutes = sprintf "%02d", abs( $offset - int($offset) ) * 60;
154 2         8 $TZ = sprintf("%+03d", int($offset)) . $minutes;
155             }
156 2         26 return join(" ",
157             ($wdays[$wday] . ','),
158             $mday,
159             $months[$mon],
160             $year+1900,
161             sprintf("%02d:%02d:%02d", $hour, $min, $sec),
162             $TZ
163             );
164             } # end sub time_to_date
165              
166             sub sendmail {
167              
168 1     1 1 19 $error = '';
169 1         26 $log = "Mail::Sendmail v. $VERSION - " . scalar(localtime()) . "\n";
170              
171 1         3 my $CRLF = "\015\012";
172 1         25 local $/ = $CRLF;
173 1         6 local $\ = ''; # to protect us from outside settings
174 1         2 local $_;
175              
176 1         4 my (%mail, $k,
177             $smtp, $server, $port, $connected, $localhost,
178             $fromaddr, $recip, @recipients, $to, $header,
179             %esmtp, @wanted_methods,
180             );
181 1     1   12 use vars qw($server_reply);
  1         2  
  1         5534  
182             # -------- a few internal subs ----------
183             sub fail {
184             # things to do before returning a sendmail failure
185 0     0 0 0 $error .= join(" ", @_) . "\n";
186 0 0       0 if ($server_reply) {
187 0         0 $error .= "Server said: $server_reply\n";
188 0 0       0 print STDERR "Server said: $server_reply\n" if $^W;
189             }
190 0         0 close S;
191 0         0 return 0;
192             }
193              
194             sub socket_write {
195 13     13 0 29 my $i;
196 13         51 for $i (0..$#_) {
197             # accept references, so we don't copy potentially big data
198 15 100       49 my $data = ref($_[$i]) ? $_[$i] : \$_[$i];
199 15 50       48 if ($mailcfg{'debug'} > 5) {
200 0 0       0 if (length($$data) < 500) {
201 0         0 print ">", $$data;
202             }
203             else {
204 0         0 print "> [...", length($$data), " bytes sent ...]\n";
205             }
206             }
207 15 50       799 print(S $$data) || return 0;
208             }
209 13         78 1;
210             }
211              
212             sub socket_read {
213 7     7 0 20 $server_reply = "";
214 7         14 do {
215 18         1311123 $_ = ;
216 18         89 $server_reply .= $_;
217             #chomp $_;
218 18 50       78 print "<$_" if $mailcfg{'debug'} > 5;
219 18 50 33     200 if (/^[45]/ or !$_) {
220 0         0 chomp $server_reply;
221 0         0 return; # return false
222             }
223             } while (/^[\d]+-/);
224 7         20 chomp $server_reply;
225 7         56 return $server_reply;
226             }
227             # -------- end of internal subs ----------
228              
229             # all config keys to lowercase, to prevent typo errors
230 1         8 foreach $k (keys %mailcfg) {
231 8 50       23 if ($k =~ /[A-Z]/) {
232 0         0 $mailcfg{lc($k)} = $mailcfg{$k};
233             }
234             }
235              
236             # redo mail hash, arranging keys case etc...
237 1         4 while (@_) {
238 5         11 $k = shift @_;
239 5 0 33     13 if (!$k and $^W) {
240 0         0 warn "Received false mail hash key: \'$k\'. Did you forget to put it in quotes?\n";
241             }
242              
243             # arrange keys case
244 5         12 $k = ucfirst lc($k);
245              
246 5         9 $k =~ s/\s*:\s*$//o; # kill colon (and possible spaces) at end, we add it later.
247             # uppercase also after "-", so people don't complain that headers case is different
248             # than in Outlook.
249 5         26 $k =~ s/-(.)/"-" . uc($1)/ge;
  0         0  
250 5         14 $mail{$k} = shift @_;
251 5 100       23 if ($k !~ /^(Message|Body|Text)$/i) {
252             # normalize possible line endings in headers
253 4         7 $mail{$k} =~ s/\015\012?/\012/go;
254 4         12 $mail{$k} =~ s/\012/$CRLF/go;
255             }
256             }
257              
258 1   33     6 $smtp = $mail{'Smtp'} || $mail{'Server'};
259 1 50 33     8 unshift @{$mailcfg{'smtp'}}, $smtp if ($smtp and $mailcfg{'smtp'}->[0] ne $smtp);
  1         5  
260              
261             # delete non-header keys, so we don't send them later as mail headers
262             # I like this syntax, but it doesn't seem to work with AS port 5.003_07:
263             # delete @mail{'Smtp', 'Server'};
264             # so instead:
265 1         3 delete $mail{'Smtp'}; delete $mail{'Server'};
  1         3  
266              
267 1   50     7 $mailcfg{'port'} = $mail{'Port'} || $mailcfg{'port'} || 25;
268 1         3 delete $mail{'Port'};
269              
270 1         2 my $auth = $mail{'Auth'};
271 1         2 delete $mail{'Auth'};
272              
273 1         2 my @parts;
274 1 50       6 push(@parts, $mail{'Message'}) if defined($mail{'Message'});
275 1 50       4 push(@parts, $mail{'Body'}) if defined($mail{'Body'});
276 1 50       23 push(@parts, $mail{'Text'}) if defined($mail{'Text'});
277 1         7 $mail{'Message'} = join("", @parts);
278              
279             # delete @mail{'Body', 'Text'};
280 1         2 delete $mail{'Body'};
281 1         2 delete $mail{'Text'};
282              
283             # Extract 'From:' e-mail address to use as envelope sender
284              
285 1   33     7 $fromaddr = $mail{'Sender'} || $mail{'From'} || $mailcfg{'from'};
286             #delete $mail{'Sender'};
287 1 50       529 unless ($fromaddr =~ /$address_rx/) {
288 0         0 return fail("Bad or missing From address: \'$fromaddr\'");
289             }
290 1         9 $fromaddr = $1;
291              
292             # add Date header if needed
293 1   33     10 $mail{Date} ||= time_to_date() ;
294 1         4 $log .= "Date: $mail{Date}\n";
295              
296             # cleanup message, and encode if needed
297 1         4 $mail{'Message'} =~ s/\r\n/\n/go; # normalize line endings, step 1 of 2 (next step after MIME encoding)
298              
299 1   50     8 $mail{'Mime-Version'} ||= '1.0';
300 1   50     9 $mail{'Content-Type'} ||= 'text/plain; charset="iso-8859-1"';
301              
302 1 50 33     11 unless ( $mail{'Content-Transfer-Encoding'}
303             || $mail{'Content-Type'} =~ /multipart/io )
304             {
305 1 50       5 if ($mailcfg{'mime'}) {
306 1         23 $mail{'Content-Transfer-Encoding'} = 'quoted-printable';
307 1         11 $mail{'Message'} = encode_qp($mail{'Message'});
308             }
309             else {
310 0         0 $mail{'Content-Transfer-Encoding'} = '8bit';
311 0 0       0 if ($mail{'Message'} =~ /[\x80-\xFF]/o) {
312 0         0 $error .= "MIME::QuotedPrint not present!\nSending 8bit characters, hoping it will come across OK.\n";
313 0 0       0 warn "MIME::QuotedPrint not present!\n",
314             "Sending 8bit characters without encoding, hoping it will come across OK.\n"
315             if $^W;
316             }
317             }
318             }
319              
320 1         4 $mail{'Message'} =~ s/^\./\.\./gom; # handle . as first character
321 1         7 $mail{'Message'} =~ s/\n/$CRLF/go; # normalize line endings, step 2.
322              
323             # Get recipients
324             { # don't warn for undefined values below
325 1         3 my @recipients;
  1         2  
326 1 50       5 push(@recipients, $mail{To}) if defined($mail{To});
327 1 50       4 push(@recipients, $mail{Cc}) if defined($mail{Cc});
328 1 50       4 push(@recipients, $mail{Bcc}) if defined($mail{Bcc});
329 1         4 $recip = join(", ", @recipients);
330             }
331              
332 1         2 delete $mail{'Bcc'};
333              
334 1         2 @recipients = ();
335 1         472 while ($recip =~ /$address_rx/go) {
336 1         10 push @recipients, $1;
337             }
338 1 50       5 unless (@recipients) {
339 0         0 return fail("No recipient!")
340             }
341              
342             # get local hostname for polite HELO
343 1   50     6 $localhost = hostname_long() || hostname() || 'localhost';
344              
345 1         34424 foreach $server ( @{$mailcfg{'smtp'}} ) {
  1         60  
346             # open socket needs to be inside this foreach loop on Linux,
347             # otherwise all servers fail if 1st one fails !??! why?
348 1 50       649 unless ( socket S, AF_INET, SOCK_STREAM, scalar(getprotobyname 'tcp') ) {
349 0         0 return fail("socket failed ($!)")
350             }
351              
352 1 50       24 print "- trying $server\n" if $mailcfg{'debug'} > 1;
353              
354 1         19 $server =~ s/\s+//go; # remove spaces just in case of a typo
355             # extract port if server name like "mail.domain.com:2525"
356 1 50       20 $port = ($server =~ s/:(\d+)$//o) ? $1 : $mailcfg{'port'};
357 1         10 $smtp = $server; # save $server for use outside foreach loop
358              
359 1         59980 my $smtpaddr = inet_aton $server;
360 1 50       16 unless ($smtpaddr) {
361 0         0 $error .= "$server not found\n";
362 0         0 next; # next server
363             }
364              
365 1         5 my $retried = 0; # reset retries for each server
366 1   33     25161 while ( ( not $connected = connect S, pack_sockaddr_in($port, $smtpaddr) )
367             and ( $retried < $mailcfg{'retries'} )
368             ) {
369 0         0 $retried++;
370 0         0 $error .= "connect to $server failed ($!)\n";
371 0 0       0 print "- connect to $server failed ($!)\n" if $mailcfg{'debug'} > 1;
372 0 0       0 print "retrying in $mailcfg{'delay'} seconds...\n" if $mailcfg{'debug'} > 1;
373 0         0 sleep $mailcfg{'delay'};
374             }
375              
376 1 50       9 if ( $connected ) {
377 1 50       10 print "- connected to $server\n" if $mailcfg{'debug'} > 3;
378 1         38 last;
379             }
380             else {
381 0         0 $error .= "connect to $server failed\n";
382 0 0       0 print "- connect to $server failed, next server...\n" if $mailcfg{'debug'} > 1;
383 0         0 next; # next server
384             }
385             }
386              
387 1 50       9 unless ( $connected ) {
388 0         0 return fail("connect to $smtp failed ($!) no (more) retries!")
389             };
390              
391             {
392 1         20 local $^W = 0; # don't warn on undefined variables
  1         19  
393             # Add info to log variable
394 1         24 $log .= "Server: $smtp Port: $port\n"
395             . "From: $fromaddr\n"
396             . "Subject: $mail{Subject}\n"
397             ;
398             }
399              
400 1         15 my($oldfh) = select(S); $| = 1; select($oldfh);
  1         11  
  1         14  
401              
402 1 50       9 socket_read()
403             || return fail("Connection error from $smtp on port $port ($_)");
404 1 50       9 socket_write("EHLO $localhost$CRLF")
405             || return fail("send EHLO error (lost connection?)");
406 1         4 my $ehlo = socket_read();
407 1 50       6 if ($ehlo) {
408             # parse EHLO response
409             map {
410 1         16 s/^\d+[- ]//;
  12         62  
411 12         40 my ($k, $v) = split /\s+/, $_, 2;
412 12 50 100     92 $esmtp{$k} = $v || 1 if $k;
413             } split(/\n/, $ehlo);
414             }
415             else {
416             # try plain HELO instead
417 0 0       0 socket_write("HELO $localhost$CRLF")
418             || return fail("send HELO error (lost connection?)");
419             }
420              
421 1 50       6 if ($auth) {
422 0 0       0 warn "AUTH requested\n" if ($mailcfg{debug} > 4);
423             # reduce wanted methods to those supported
424 0         0 my @methods = grep {$esmtp{'AUTH'}=~/(^|\s)$_(\s|$)/i}
425 0         0 grep {$auth_support =~ /(^|\s)$_(\s|$)/i}
426 0         0 grep /\S/, split(/\s+/, $auth->{method});
427              
428 0 0       0 if (@methods) {
429             # try to authenticate
430              
431 0 0       0 if (exists $auth->{pass}) {
432 0         0 $auth->{password} = $auth->{pass};
433             }
434              
435 0         0 my $method = uc $methods[0];
436 0 0       0 _require_base64() || fail("Could not use MIME::Base64 module required for authentication");
437 0 0       0 if ($method eq "LOGIN") {
    0          
    0          
    0          
438 0 0       0 print STDERR "Trying AUTH LOGIN\n" if ($mailcfg{debug} > 9);
439 0 0       0 socket_write("AUTH LOGIN$CRLF")
440             || return fail("send AUTH LOGIN failed (lost connection?)");
441 0 0       0 socket_read()
442             || return fail("AUTH LOGIN failed: $server_reply");
443 0 0       0 socket_write(encode_base64($auth->{user},$CRLF))
444             || return fail("send LOGIN username failed (lost connection?)");
445 0 0       0 socket_read()
446             || return fail("LOGIN username failed: $server_reply");
447 0 0       0 socket_write(encode_base64($auth->{password},$CRLF))
448             || return fail("send LOGIN password failed (lost connection?)");
449 0 0       0 socket_read()
450             || return fail("LOGIN password failed: $server_reply");
451             }
452             elsif ($method eq "PLAIN") {
453 0 0       0 warn "Trying AUTH PLAIN\n" if ($mailcfg{debug} > 9);
454             socket_write(
455             "AUTH PLAIN "
456 0 0       0 . encode_base64(join("\0", $auth->{user}, $auth->{user}, $auth->{password}), $CRLF)
457             ) || return fail("send AUTH PLAIN failed (lost connection?)");
458 0 0       0 socket_read()
459             || return fail("AUTH PLAIN failed: $server_reply");
460             }
461             elsif ($method eq "CRAM-MD5") {
462 0 0       0 _require_md5() || fail("Could not use Digest::MD5 module required for authentication");
463 0 0       0 warn "Trying AUTH CRAM-MD5\n" if ($mailcfg{debug} > 9);
464 0 0       0 socket_write("AUTH CRAM-MD5$CRLF")
465             || return fail("send CRAM-MD5 failed (lost connection?)");
466 0   0     0 my $challenge = socket_read()
467             || return fail("AUTH CRAM-MD5 failed: $server_reply");
468 0         0 $challenge =~ s/^\d+\s+//;
469 0         0 my $response = _hmac_md5($auth->{password}, decode_base64($challenge));
470 0 0       0 socket_write(encode_base64("$auth->{user} $response", $CRLF))
471             || return fail("AUTH CRAM-MD5 failed: $server_reply");
472 0 0       0 socket_read()
473             || return fail("AUTH CRAM-MD5 failed: $server_reply");
474             }
475             elsif ($method eq "DIGEST-MD5") {
476 0 0       0 _require_md5() || fail("Could not use Digest::MD5 module required for authentication");
477 0 0       0 warn "Trying AUTH DIGEST-MD5\n" if ($mailcfg{debug} > 9);
478 0 0       0 socket_write("AUTH DIGEST-MD5$CRLF")
479             || return fail("send CRAM-MD5 failed (lost connection?)");
480 0   0     0 my $challenge = socket_read()
481             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
482 0         0 $challenge =~ s/^\d+\s+//; $challenge =~ s/[\r\n]+$//;
  0         0  
483 0 0       0 warn "\nCHALLENGE=", decode_base64($challenge), "\n" if ($mailcfg{debug} > 10);
484 0         0 my $response = _digest_md5($auth->{user}, $auth->{password}, decode_base64($challenge), $auth->{realm});
485 0 0       0 warn "\nRESPONSE=$response\n" if ($mailcfg{debug} > 10);
486 0 0       0 socket_write(encode_base64($response, ""), $CRLF)
487             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
488 0   0     0 my $status = socket_read()
489             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
490 0 0       0 if ($status =~ /^3/) {
491 0 0       0 socket_write($CRLF)
492             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
493 0 0       0 socket_read()
494             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
495             }
496             }
497             else {
498 0         0 return fail("$method not supported (and wrongly advertised as supported by this silly module)\n");
499             }
500 0         0 $log .= "AUTH $method succeeded as user $auth->{user}\n";
501             }
502             else {
503 0         0 $esmtp{'AUTH'} =~ s/(^\s+|\s+$)//g; # cleanup for printig it below
504 0 0       0 if ($auth->{required}) {
505 0         0 return fail("Required AUTH method '$auth->{method}' not supported. "
506             ."(Server supports '$esmtp{'AUTH'}'. Module supports: '$auth_support')");
507             }
508             else {
509 0         0 warn "No common authentication method! Requested: '$auth->{method}'. Server supports '$esmtp{'AUTH'}'. Module supports: '$auth_support'. Skipping authentication\n";
510             }
511             }
512             }
513 1 50       27 socket_write("MAIL FROM:<$fromaddr>$CRLF")
514             || return fail("send MAIL FROM: error");
515 1 50       17 socket_read()
516             || return fail("MAIL FROM: error ($_)");
517              
518 1         3 my $to_ok = 0;
519 1         3 foreach $to (@recipients) {
520 1 50       7 socket_write("RCPT TO:<$to>$CRLF")
521             || return fail("send RCPT TO: error");
522 1 50       8 if (socket_read()) {
523 1         5 $log .= "To: $to\n";
524 1         4 $to_ok++;
525             } else {
526 0         0 $log .= "FAILED To: $to ($server_reply)";
527 0         0 $error .= "Bad recipient <$to>: $server_reply\n";
528             }
529             }
530 1 50       42 unless ($to_ok) {
531 0         0 return fail("No valid recipient");
532             }
533              
534             # start data part
535              
536 1 50       10 socket_write("DATA$CRLF")
537             || return fail("send DATA error");
538 1 50       3 socket_read()
539             || return fail("DATA error ($_)");
540              
541             # print headers
542 1         52 foreach $header (keys %mail) {
543 8 100       23 next if $header eq "Message";
544 7         44 $mail{$header} =~ s/\s+$//o; # kill possible trailing garbage
545 7 50       28 socket_write("$header: $mail{$header}$CRLF")
546             || return fail("send $header: error");
547             };
548              
549             #- test disconnecting from network here, to see what happens
550             #- print STDERR "DISCONNECT NOW!\n";
551             #- sleep 4;
552             #- print STDERR "trying to continue, expecting an error... \n";
553              
554             # send message body (passed as a reference, in case it's big)
555 1 50       14 socket_write($CRLF, \$mail{'Message'}, "$CRLF.$CRLF")
556             || return fail("send message error");
557 1 50       23 socket_read()
558             || return fail("message transmission error ($_)");
559 1         14 $log .= "\nResult: $_";
560              
561             # finish
562 1 50       36 socket_write("QUIT$CRLF")
563             || return fail("send QUIT error");
564 1         5 socket_read();
565 1         103 close S;
566              
567 1         93 return 1;
568             } # end sub sendmail
569              
570             1;
571             __END__