| Filename | /usr/share/perl5/Mail/Sendmail.pm |
| Statements | Executed 36 statements in 4.22ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1.03ms | 1.30ms | Mail::Sendmail::BEGIN@57 |
| 1 | 1 | 1 | 933µs | 2.33ms | Mail::Sendmail::BEGIN@54 |
| 1 | 1 | 1 | 766µs | 1.09ms | Mail::Sendmail::BEGIN@56 |
| 1 | 1 | 1 | 21µs | 66µs | Mail::Sendmail::BEGIN@55 |
| 1 | 1 | 1 | 18µs | 22µs | Mail::Sendmail::BEGIN@38 |
| 1 | 1 | 1 | 11µs | 42µs | Mail::Sendmail::BEGIN@183 |
| 1 | 1 | 1 | 9µs | 151µs | Mail::Sendmail::BEGIN@39 |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::_digest_md5 |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::_hmac_md5 |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::_require_base64 |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::_require_md5 |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::fail |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::make_cnonce |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::sendmail |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::socket_read |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::socket_write |
| 0 | 0 | 0 | 0s | 0s | Mail::Sendmail::time_to_date |
| 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 | |||||
| - - | |||||
| 12 | 1 | 1µ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 | 7µ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 | 800ns | require Exporter; | ||
| 38 | 3 | 44µs | 2 | 26µs | # spent 22µs (18+4) within Mail::Sendmail::BEGIN@38 which was called:
# once (18µs+4µs) by C4::Letters::BEGIN@24 at line 38 # spent 22µs making 1 call to Mail::Sendmail::BEGIN@38
# spent 4µs making 1 call to strict::import |
| 39 | 1 | 5µs | 1 | 142µs | # spent 151µs (9+142) within Mail::Sendmail::BEGIN@39 which was called:
# once (9µs+142µs) by C4::Letters::BEGIN@24 at line 52 # spent 142µ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 | 2 | 30µs | 1 | 151µs | ); # spent 151µs making 1 call to Mail::Sendmail::BEGIN@39 |
| 53 | |||||
| 54 | 3 | 238µs | 2 | 3.22ms | # spent 2.33ms (933µs+1.39) within Mail::Sendmail::BEGIN@54 which was called:
# once (933µs+1.39ms) by C4::Letters::BEGIN@24 at line 54 # spent 2.33ms making 1 call to Mail::Sendmail::BEGIN@54
# spent 894µs making 1 call to Exporter::import |
| 55 | 3 | 41µs | 2 | 111µs | # spent 66µs (21+45) within Mail::Sendmail::BEGIN@55 which was called:
# once (21µs+45µs) by C4::Letters::BEGIN@24 at line 55 # spent 66µs making 1 call to Mail::Sendmail::BEGIN@55
# spent 45µs making 1 call to Exporter::import |
| 56 | 3 | 130µs | 2 | 1.13ms | # spent 1.09ms (766µs+322µs) within Mail::Sendmail::BEGIN@56 which was called:
# once (766µs+322µs) by C4::Letters::BEGIN@24 at line 56 # spent 1.09ms making 1 call to Mail::Sendmail::BEGIN@56
# spent 41µs making 1 call to Exporter::import |
| 57 | 3 | 884µs | 2 | 1.34ms | # spent 1.30ms (1.03+274µs) within Mail::Sendmail::BEGIN@57 which was called:
# once (1.03ms+274µs) by C4::Letters::BEGIN@24 at line 57 # spent 1.30ms making 1 call to Mail::Sendmail::BEGIN@57
# spent 38µs making 1 call to Exporter::import |
| 58 | |||||
| 59 | #use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex); | ||||
| 60 | |||||
| 61 | 1 | 600ns | $auth_support = 'DIGEST-MD5 CRAM-MD5 PLAIN LOGIN'; | ||
| 62 | |||||
| 63 | # use MIME::QuotedPrint if available and configured in %mailcfg | ||||
| 64 | 1 | 42µs | eval("use MIME::QuotedPrint"); # spent 21µs executing statements in string eval # includes 19µs spent executing 1 call to 1 sub defined therein. | ||
| 65 | 1 | 2µs | $mailcfg{'mime'} &&= (!$@); | ||
| 66 | |||||
| 67 | 1 | 8µs | @ISA = qw(Exporter); | ||
| 68 | 1 | 800ns | @EXPORT = qw(&sendmail); | ||
| 69 | 1 | 1µs | @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 | 600ns | my $word_rx = '[\x21\x23-\x27\x2A-\x2B\x2D\x2F\w\x3D\x3F]+'; | ||
| 82 | 1 | 2µs | my $user_rx = $word_rx # valid chars | ||
| 83 | .'(?:\.' . $word_rx . ')*' # possibly more words preceded by a dot | ||||
| 84 | ; | ||||
| 85 | 1 | 500ns | my $dom_rx = '\w[-\w]*(?:\.\w[-\w]*)*'; # less valid chars in domain names | ||
| 86 | 1 | 300ns | my $ip_rx = '\[\d{1,3}(?:\.\d{1,3}){3}\]'; | ||
| 87 | |||||
| 88 | 1 | 2µ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 | 3 | 2.76ms | 2 | 72µs | # spent 42µs (11+31) within Mail::Sendmail::BEGIN@183 which was called:
# once (11µs+31µs) by C4::Letters::BEGIN@24 at line 183 # spent 42µs making 1 call to Mail::Sendmail::BEGIN@183
# spent 31µ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 | 13µs | 1; | ||
| 569 | __END__ |