← Index
NYTProf Performance Profile   « line view »
For svc/members/upsert
  Run on Tue Jan 13 11:50:22 2015
Reported on Tue Jan 13 12:09:50 2015

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