Filename | /usr/share/perl5/Mail/Sendmail.pm |
Statements | Executed 29 statements in 4.83ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 2.93ms | 3.83ms | BEGIN@54 | Mail::Sendmail::
1 | 1 | 1 | 1.53ms | 1.68ms | BEGIN@57 | Mail::Sendmail::
1 | 1 | 1 | 1.08ms | 1.28ms | BEGIN@56 | Mail::Sendmail::
1 | 1 | 1 | 11µs | 33µs | BEGIN@55 | Mail::Sendmail::
1 | 1 | 1 | 11µs | 22µs | BEGIN@38 | Mail::Sendmail::
1 | 1 | 1 | 8µs | 24µs | BEGIN@183 | Mail::Sendmail::
1 | 1 | 1 | 6µs | 96µs | BEGIN@39 | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | _digest_md5 | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | _hmac_md5 | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | _require_base64 | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | _require_md5 | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | fail | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | make_cnonce | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | sendmail | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | socket_read | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | socket_write | Mail::Sendmail::
0 | 0 | 0 | 0s | 0s | time_to_date | Mail::Sendmail::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package 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 | |||||
8 | Mail::Sendmail - Simple platform independent mailer | ||||
9 | |||||
10 | =cut | ||||
11 | |||||
12 | 1 | 2µ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 | |||||
18 | 1 | 4µ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 | |||||
37 | 1 | 600ns | require Exporter; | ||
38 | 2 | 34µs | 2 | 32µ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 # spent 22µs making 1 call to Mail::Sendmail::BEGIN@38
# spent 11µs making 1 call to strict::import |
39 | 1 | 3µs | 1 | 90µ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 # 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 | ||||
52 | 1 | 20µs | 1 | 96µs | ); # spent 96µs making 1 call to Mail::Sendmail::BEGIN@39 |
53 | |||||
54 | 2 | 705µs | 2 | 4.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 # spent 3.83ms making 1 call to Mail::Sendmail::BEGIN@54
# spent 364µs making 1 call to Exporter::import |
55 | 2 | 26µs | 2 | 56µ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 # spent 33µs making 1 call to Mail::Sendmail::BEGIN@55
# spent 22µs making 1 call to Exporter::import |
56 | 2 | 655µs | 2 | 1.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 # spent 1.28ms making 1 call to Mail::Sendmail::BEGIN@56
# spent 21µs making 1 call to Exporter::import |
57 | 2 | 1.45ms | 2 | 1.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 # 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 | |||||
61 | 1 | 100ns | $auth_support = 'DIGEST-MD5 CRAM-MD5 PLAIN LOGIN'; | ||
62 | |||||
63 | # use MIME::QuotedPrint if available and configured in %mailcfg | ||||
64 | 1 | 27µs | eval("use MIME::QuotedPrint"); # spent 12µs executing statements in string eval # includes 11µs spent executing 1 call to 1 sub defined therein. | ||
65 | 1 | 800ns | $mailcfg{'mime'} &&= (!$@); | ||
66 | |||||
67 | 1 | 4µs | @ISA = qw(Exporter); | ||
68 | 1 | 400ns | @EXPORT = qw(&sendmail); | ||
69 | 1 | 500ns | @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 | |||||
81 | 1 | 200ns | my $word_rx = '[\x21\x23-\x27\x2A-\x2B\x2D\x2F\w\x3D\x3F]+'; | ||
82 | 1 | 900ns | my $user_rx = $word_rx # valid chars | ||
83 | .'(?:\.' . $word_rx . ')*' # possibly more words preceded by a dot | ||||
84 | ; | ||||
85 | 1 | 100ns | my $dom_rx = '\w[-\w]*(?:\.\w[-\w]*)*'; # less valid chars in domain names | ||
86 | 1 | 200ns | my $ip_rx = '\[\d{1,3}(?:\.\d{1,3}){3}\]'; | ||
87 | |||||
88 | 1 | 1µs | $address_rx = '((' . $user_rx . ')\@(' . $dom_rx . '|' . $ip_rx . '))'; | ||
89 | ; # v. 0.61 | ||||
90 | |||||
91 | sub _require_md5 { | ||||
92 | eval { require Digest::MD5; Digest::MD5->import(qw(md5 md5_hex)); }; | ||||
93 | $error .= $@ if $@; | ||||
94 | return ($@ ? undef : 1); | ||||
95 | } | ||||
96 | |||||
97 | sub _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 | |||||
105 | sub _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 | |||||
114 | sub _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 | |||||
132 | sub 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 | |||||
140 | sub 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 | |||||
168 | sub 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 | ); | ||||
183 | 2 | 1.89ms | 2 | 41µ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 # 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 | |||||
568 | 1 | 7µs | 1; | ||
569 | __END__ |