File Coverage

blib/lib/Mail/Sendmail.pm
Criterion Covered Total %
statement 167 286 58.3
branch 51 184 27.7
condition 15 48 31.2
subroutine 13 19 68.4
pod 2 6 33.3
total 248 543 45.6


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             our $VERSION = "0.83";
14              
15             require 5.006;
16              
17 2     2   127741 use strict;
  2         2  
  2         57  
18 2     2   10 use warnings;
  2         2  
  2         94  
19              
20 2     2   330 use parent 'Exporter';
  2         218  
  2         9  
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 2     2   672 use Socket;
  2         3247  
  2         756  
54 2     2   813 use Time::Local; # for automatic time zone detection
  2         2900  
  2         142  
55 2     2   818 use Sys::Hostname; # for use of hostname in HELO
  2         2015  
  2         100  
56 2     2   756 use Sys::Hostname::Long; # for use of hostname in HELO
  2         1906  
  2         1931  
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 2     2   908 eval("use MIME::QuotedPrint");
  2         2633  
  2         109  
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 4   33 4 1 120900 my $time = $_[0] || time(); # default to now if no argument
142              
143 4         139 my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
144 4         31 my @wdays = qw(Sun Mon Tue Wed Thu Fri Sat);
145              
146 4         98 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
147             = localtime($time);
148              
149 4         34 my $TZ = $mailcfg{'tz'};
150 4 50       17 if ( $TZ eq "" ) {
151             # offset in hours
152 4         116 my $offset = sprintf "%.1f", (timegm(localtime) - time) / 3600;
153 4         326 my $minutes = sprintf "%02d", abs( $offset - int($offset) ) * 60;
154 4         27 $TZ = sprintf("%+03d", int($offset)) . $minutes;
155             }
156 4         57 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 3     3 1 224026 $error = '';
169 3         162 $log = "Mail::Sendmail v. $VERSION - " . scalar(localtime()) . "\n";
170              
171 3         14 my $CRLF = "\015\012";
172 3         66 local $/ = $CRLF;
173 3         70 local $\ = ''; # to protect us from outside settings
174 3         9 local $_;
175              
176 3         24 my (%mail, $k,
177             $smtp, $server, $port, $connected, $localhost,
178             $fromaddr, $recip, @recipients, $to, $header,
179             %esmtp, @wanted_methods,
180             );
181 2     2   15 use vars qw($server_reply);
  2         2  
  2         6438  
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 39     39 0 61 my $i;
196 39         147 for $i (0..$#_) {
197             # accept references, so we don't copy potentially big data
198 45 100       170 my $data = ref($_[$i]) ? $_[$i] : \$_[$i];
199 45 50       142 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 45 50       1631 print(S $$data) || return 0;
208             }
209 39         148 1;
210             }
211              
212             sub socket_read {
213 21     21 0 55 $server_reply = "";
214 21         33 do {
215 25         1017373 $_ = ;
216 25         210 $server_reply .= $_;
217             #chomp $_;
218 25 50       174 print "<$_" if $mailcfg{'debug'} > 5;
219 25 50 33     378 if (/^[45]/ or !$_) {
220 0         0 chomp $server_reply;
221 0         0 return; # return false
222             }
223             } while (/^[\d]+-/);
224 21         145 chomp $server_reply;
225 21         257 return $server_reply;
226             }
227             # -------- end of internal subs ----------
228              
229             # all config keys to lowercase, to prevent typo errors
230 3         116 foreach $k (keys %mailcfg) {
231 24 50       64 if ($k =~ /[A-Z]/) {
232 0         0 $mailcfg{lc($k)} = $mailcfg{$k};
233             }
234             }
235              
236             # redo mail hash, arranging keys case etc...
237 3         27 while (@_) {
238 15         30 $k = shift @_;
239 15 0 33     44 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 15         32 $k = ucfirst lc($k);
245              
246 15         36 $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 15         18 $k =~ s/-(.)/"-" . uc($1)/ge;
  0         0  
250 15         133 $mail{$k} = shift @_;
251 15 100       88 if ($k !~ /^(Message|Body|Text)$/i) {
252             # Strip any CR/LF characters from header values to prevent
253             # CRLF injection (SMTP header injection). Attacker-controlled
254             # header values containing "\nBcc: attacker@evil.com" would
255             # otherwise inject new headers or body content.
256             # See CWE-93 (Improper Neutralization of CRLF Sequences) and
257             # the equivalent fixes in Email::MIME (CVE-2024-4140) and
258             # many other mail libraries.
259 12 100       109 if ($mail{$k} =~ /[\015\012]/) {
260 1         58 warn "Mail::Sendmail: stripping CR/LF from $k header to "
261             . "prevent CRLF injection\n";
262 1         20 $mail{$k} =~ s/[\015\012]+//g;
263             }
264             }
265             }
266              
267 3   33     20 $smtp = $mail{'Smtp'} || $mail{'Server'};
268 3 50 33     66 unshift @{$mailcfg{'smtp'}}, $smtp if ($smtp and $mailcfg{'smtp'}->[0] ne $smtp);
  3         59  
269              
270             # delete non-header keys, so we don't send them later as mail headers
271             # I like this syntax, but it doesn't seem to work with AS port 5.003_07:
272             # delete @mail{'Smtp', 'Server'};
273             # so instead:
274 3         17 delete $mail{'Smtp'}; delete $mail{'Server'};
  3         5  
275              
276 3   50     59 $mailcfg{'port'} = $mail{'Port'} || $mailcfg{'port'} || 25;
277 3         6 delete $mail{'Port'};
278              
279 3         15 my $auth = $mail{'Auth'};
280 3         11 delete $mail{'Auth'};
281              
282 3         20 my @parts;
283 3 50       12 push(@parts, $mail{'Message'}) if defined($mail{'Message'});
284 3 50       8 push(@parts, $mail{'Body'}) if defined($mail{'Body'});
285 3 50       18 push(@parts, $mail{'Text'}) if defined($mail{'Text'});
286 3         15 $mail{'Message'} = join("", @parts);
287              
288             # delete @mail{'Body', 'Text'};
289 3         6 delete $mail{'Body'};
290 3         33 delete $mail{'Text'};
291              
292             # Extract 'From:' e-mail address to use as envelope sender
293              
294 3   33     47 $fromaddr = $mail{'Sender'} || $mail{'From'} || $mailcfg{'from'};
295             #delete $mail{'Sender'};
296 3 50       796 unless ($fromaddr =~ /$address_rx/) {
297 0         0 return fail("Bad or missing From address: \'$fromaddr\'");
298             }
299 3         39 $fromaddr = $1;
300              
301             # add Date header if needed
302 3   33     96 $mail{Date} ||= time_to_date() ;
303 3         8 $log .= "Date: $mail{Date}\n";
304              
305             # cleanup message, and encode if needed
306 3         9 $mail{'Message'} =~ s/\r\n/\n/go; # normalize line endings, step 1 of 2 (next step after MIME encoding)
307              
308 3   50     22 $mail{'Mime-Version'} ||= '1.0';
309 3   50     51 $mail{'Content-Type'} ||= 'text/plain; charset="iso-8859-1"';
310              
311 3 50 33     20 unless ( $mail{'Content-Transfer-Encoding'}
312             || $mail{'Content-Type'} =~ /multipart/io )
313             {
314 3 100       9 if ($mailcfg{'mime'}) {
315 1         4 $mail{'Content-Transfer-Encoding'} = 'quoted-printable';
316 1         9 $mail{'Message'} = encode_qp($mail{'Message'});
317             }
318             else {
319 2         37 $mail{'Content-Transfer-Encoding'} = '8bit';
320 2 50       6 if ($mail{'Message'} =~ /[\x80-\xFF]/o) {
321 0         0 $error .= "MIME::QuotedPrint not present!\nSending 8bit characters, hoping it will come across OK.\n";
322 0 0       0 warn "MIME::QuotedPrint not present!\n",
323             "Sending 8bit characters without encoding, hoping it will come across OK.\n"
324             if $^W;
325             }
326             }
327             }
328              
329 3         32 $mail{'Message'} =~ s/^\./\.\./gom; # handle . as first character
330 3         30 $mail{'Message'} =~ s/\n/$CRLF/go; # normalize line endings, step 2.
331              
332             # Get recipients
333             { # don't warn for undefined values below
334 3         5 my @recipients;
  3         3  
335 3 50       19 push(@recipients, $mail{To}) if defined($mail{To});
336 3 50       8 push(@recipients, $mail{Cc}) if defined($mail{Cc});
337 3 50       6 push(@recipients, $mail{Bcc}) if defined($mail{Bcc});
338 3         8 $recip = join(", ", @recipients);
339             }
340              
341 3         3 delete $mail{'Bcc'};
342              
343 3         6 @recipients = ();
344 3         520 while ($recip =~ /$address_rx/go) {
345 3         31 push @recipients, $1;
346             }
347 3 50       9 unless (@recipients) {
348 0         0 return fail("No recipient!")
349             }
350              
351             # get local hostname for polite HELO
352 3   50     37 $localhost = hostname_long() || hostname() || 'localhost';
353              
354 3         61110 foreach $server ( @{$mailcfg{'smtp'}} ) {
  3         45  
355             # open socket needs to be inside this foreach loop on Linux,
356             # otherwise all servers fail if 1st one fails !??! why?
357 3 50       896 unless ( socket S, AF_INET, SOCK_STREAM, scalar(getprotobyname 'tcp') ) {
358 0         0 return fail("socket failed ($!)")
359             }
360              
361 3 50       75 print "- trying $server\n" if $mailcfg{'debug'} > 1;
362              
363 3         37 $server =~ s/\s+//go; # remove spaces just in case of a typo
364             # extract port if server name like "mail.domain.com:2525"
365 3 100       91 $port = ($server =~ s/:(\d+)$//o) ? $1 : $mailcfg{'port'};
366 3         18 $smtp = $server; # save $server for use outside foreach loop
367              
368 3         27660 my $smtpaddr = inet_aton $server;
369 3 50       31 unless ($smtpaddr) {
370 0         0 $error .= "$server not found\n";
371 0         0 next; # next server
372             }
373              
374 3         12 my $retried = 0; # reset retries for each server
375 3   33     111365 while ( ( not $connected = connect S, pack_sockaddr_in($port, $smtpaddr) )
376             and ( $retried < $mailcfg{'retries'} )
377             ) {
378 0         0 $retried++;
379 0         0 $error .= "connect to $server failed ($!)\n";
380 0 0       0 print "- connect to $server failed ($!)\n" if $mailcfg{'debug'} > 1;
381 0 0       0 print "retrying in $mailcfg{'delay'} seconds...\n" if $mailcfg{'debug'} > 1;
382 0         0 sleep $mailcfg{'delay'};
383             }
384              
385 3 50       27 if ( $connected ) {
386 3 50       74 print "- connected to $server\n" if $mailcfg{'debug'} > 3;
387 3         15 last;
388             }
389             else {
390 0         0 $error .= "connect to $server failed\n";
391 0 0       0 print "- connect to $server failed, next server...\n" if $mailcfg{'debug'} > 1;
392 0         0 next; # next server
393             }
394             }
395              
396 3 50       14 unless ( $connected ) {
397 0         0 return fail("connect to $smtp failed ($!) no (more) retries!")
398             };
399              
400             {
401 3         45 local $^W = 0; # don't warn on undefined variables
  3         68  
402             # Add info to log variable
403 3         36 $log .= "Server: $smtp Port: $port\n"
404             . "From: $fromaddr\n"
405             . "Subject: $mail{Subject}\n"
406             ;
407             }
408              
409 3         26 my($oldfh) = select(S); $| = 1; select($oldfh);
  3         21  
  3         28  
410              
411 3 50       19 socket_read()
412             || return fail("Connection error from $smtp on port $port ($_)");
413 3 50       53 socket_write("EHLO $localhost$CRLF")
414             || return fail("send EHLO error (lost connection?)");
415 3         11 my $ehlo = socket_read();
416 3 50       20 if ($ehlo) {
417             # parse EHLO response
418             map {
419 3         36 s/^\d+[- ]//;
  7         79  
420 7         55 my ($k, $v) = split /\s+/, $_, 2;
421 7 50 100     173 $esmtp{$k} = $v || 1 if $k;
422             } split(/\n/, $ehlo);
423             }
424             else {
425             # try plain HELO instead
426 0 0       0 socket_write("HELO $localhost$CRLF")
427             || return fail("send HELO error (lost connection?)");
428             }
429              
430 3 50       11 if ($auth) {
431 0 0       0 warn "AUTH requested\n" if ($mailcfg{debug} > 4);
432             # reduce wanted methods to those supported
433 0         0 my @methods = grep {$esmtp{'AUTH'}=~/(^|\s)$_(\s|$)/i}
434 0         0 grep {$auth_support =~ /(^|\s)$_(\s|$)/i}
435 0         0 grep /\S/, split(/\s+/, $auth->{method});
436              
437 0 0       0 if (@methods) {
438             # try to authenticate
439              
440 0 0       0 if (exists $auth->{pass}) {
441 0         0 $auth->{password} = $auth->{pass};
442             }
443              
444 0         0 my $method = uc $methods[0];
445 0 0       0 _require_base64() || fail("Could not use MIME::Base64 module required for authentication");
446 0 0       0 if ($method eq "LOGIN") {
    0          
    0          
    0          
447 0 0       0 print STDERR "Trying AUTH LOGIN\n" if ($mailcfg{debug} > 9);
448 0 0       0 socket_write("AUTH LOGIN$CRLF")
449             || return fail("send AUTH LOGIN failed (lost connection?)");
450 0 0       0 socket_read()
451             || return fail("AUTH LOGIN failed: $server_reply");
452 0 0       0 socket_write(encode_base64($auth->{user},$CRLF))
453             || return fail("send LOGIN username failed (lost connection?)");
454 0 0       0 socket_read()
455             || return fail("LOGIN username failed: $server_reply");
456 0 0       0 socket_write(encode_base64($auth->{password},$CRLF))
457             || return fail("send LOGIN password failed (lost connection?)");
458 0 0       0 socket_read()
459             || return fail("LOGIN password failed: $server_reply");
460             }
461             elsif ($method eq "PLAIN") {
462 0 0       0 warn "Trying AUTH PLAIN\n" if ($mailcfg{debug} > 9);
463             socket_write(
464             "AUTH PLAIN "
465 0 0       0 . encode_base64(join("\0", $auth->{user}, $auth->{user}, $auth->{password}), $CRLF)
466             ) || return fail("send AUTH PLAIN failed (lost connection?)");
467 0 0       0 socket_read()
468             || return fail("AUTH PLAIN failed: $server_reply");
469             }
470             elsif ($method eq "CRAM-MD5") {
471 0 0       0 _require_md5() || fail("Could not use Digest::MD5 module required for authentication");
472 0 0       0 warn "Trying AUTH CRAM-MD5\n" if ($mailcfg{debug} > 9);
473 0 0       0 socket_write("AUTH CRAM-MD5$CRLF")
474             || return fail("send CRAM-MD5 failed (lost connection?)");
475 0   0     0 my $challenge = socket_read()
476             || return fail("AUTH CRAM-MD5 failed: $server_reply");
477 0         0 $challenge =~ s/^\d+\s+//;
478 0         0 my $response = _hmac_md5($auth->{password}, decode_base64($challenge));
479 0 0       0 socket_write(encode_base64("$auth->{user} $response", $CRLF))
480             || return fail("AUTH CRAM-MD5 failed: $server_reply");
481 0 0       0 socket_read()
482             || return fail("AUTH CRAM-MD5 failed: $server_reply");
483             }
484             elsif ($method eq "DIGEST-MD5") {
485 0 0       0 _require_md5() || fail("Could not use Digest::MD5 module required for authentication");
486 0 0       0 warn "Trying AUTH DIGEST-MD5\n" if ($mailcfg{debug} > 9);
487 0 0       0 socket_write("AUTH DIGEST-MD5$CRLF")
488             || return fail("send CRAM-MD5 failed (lost connection?)");
489 0   0     0 my $challenge = socket_read()
490             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
491 0         0 $challenge =~ s/^\d+\s+//; $challenge =~ s/[\r\n]+$//;
  0         0  
492 0 0       0 warn "\nCHALLENGE=", decode_base64($challenge), "\n" if ($mailcfg{debug} > 10);
493 0         0 my $response = _digest_md5($auth->{user}, $auth->{password}, decode_base64($challenge), $auth->{realm});
494 0 0       0 warn "\nRESPONSE=$response\n" if ($mailcfg{debug} > 10);
495 0 0       0 socket_write(encode_base64($response, ""), $CRLF)
496             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
497 0   0     0 my $status = socket_read()
498             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
499 0 0       0 if ($status =~ /^3/) {
500 0 0       0 socket_write($CRLF)
501             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
502 0 0       0 socket_read()
503             || return fail("AUTH DIGEST-MD5 failed: $server_reply");
504             }
505             }
506             else {
507 0         0 return fail("$method not supported (and wrongly advertised as supported by this silly module)\n");
508             }
509 0         0 $log .= "AUTH $method succeeded as user $auth->{user}\n";
510             }
511             else {
512 0         0 $esmtp{'AUTH'} =~ s/(^\s+|\s+$)//g; # cleanup for printig it below
513 0 0       0 if ($auth->{required}) {
514 0         0 return fail("Required AUTH method '$auth->{method}' not supported. "
515             ."(Server supports '$esmtp{'AUTH'}'. Module supports: '$auth_support')");
516             }
517             else {
518 0         0 warn "No common authentication method! Requested: '$auth->{method}'. Server supports '$esmtp{'AUTH'}'. Module supports: '$auth_support'. Skipping authentication\n";
519             }
520             }
521             }
522 3 50       61 socket_write("MAIL FROM:<$fromaddr>$CRLF")
523             || return fail("send MAIL FROM: error");
524 3 50       13 socket_read()
525             || return fail("MAIL FROM: error ($_)");
526              
527 3         13 my $to_ok = 0;
528 3         9 foreach $to (@recipients) {
529 3 50       13 socket_write("RCPT TO:<$to>$CRLF")
530             || return fail("send RCPT TO: error");
531 3 50       25 if (socket_read()) {
532 3         14 $log .= "To: $to\n";
533 3         12 $to_ok++;
534             } else {
535 0         0 $log .= "FAILED To: $to ($server_reply)";
536 0         0 $error .= "Bad recipient <$to>: $server_reply\n";
537             }
538             }
539 3 50       65 unless ($to_ok) {
540 0         0 return fail("No valid recipient");
541             }
542              
543             # start data part
544              
545 3 50       44 socket_write("DATA$CRLF")
546             || return fail("send DATA error");
547 3 50       9 socket_read()
548             || return fail("DATA error ($_)");
549              
550             # print headers
551 3         50 foreach $header (keys %mail) {
552 24 100       78 next if $header eq "Message";
553 21         134 $mail{$header} =~ s/\s+$//o; # kill possible trailing garbage
554 21 50       79 socket_write("$header: $mail{$header}$CRLF")
555             || return fail("send $header: error");
556             };
557              
558             #- test disconnecting from network here, to see what happens
559             #- print STDERR "DISCONNECT NOW!\n";
560             #- sleep 4;
561             #- print STDERR "trying to continue, expecting an error... \n";
562              
563             # send message body (passed as a reference, in case it's big)
564 3 50       44 socket_write($CRLF, \$mail{'Message'}, "$CRLF.$CRLF")
565             || return fail("send message error");
566 3 50       10 socket_read()
567             || return fail("message transmission error ($_)");
568 3         23 $log .= "\nResult: $_";
569              
570             # finish
571 3 50       66 socket_write("QUIT$CRLF")
572             || return fail("send QUIT error");
573 3         22 socket_read();
574 3         337 close S;
575              
576 3         203 return 1;
577             } # end sub sendmail
578              
579             1;
580             __END__