File Coverage

blib/lib/Mail/Sendmail.pm
Criterion Covered Total %
statement 161 282 57.0
branch 46 182 25.2
condition 15 48 31.2
subroutine 12 18 66.6
pod 2 6 33.3
total 236 536 44.0


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