1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Email::SMTP; 10 11use strict; 12use warnings; 13 14use Net::SMTP; 15 16our @ObjectDependencies = ( 17 'Kernel::Config', 18 'Kernel::System::DB', 19 'Kernel::System::Encode', 20 'Kernel::System::Log', 21 'Kernel::System::CommunicationLog', 22); 23 24sub new { 25 my ( $Type, %Param ) = @_; 26 27 # allocate new hash for object 28 my $Self = {%Param}; 29 bless( $Self, $Type ); 30 31 # debug 32 $Self->{Debug} = $Param{Debug} || 0; 33 if ( $Self->{Debug} > 2 ) { 34 35 # shown on STDERR 36 $Self->{SMTPDebug} = 1; 37 } 38 39 ( $Self->{SMTPType} ) = ( $Type =~ m/::Email::(.*)$/i ); 40 41 return $Self; 42} 43 44sub Check { 45 my ( $Self, %Param ) = @_; 46 47 $Param{CommunicationLogObject}->ObjectLogStart( 48 ObjectLogType => 'Connection', 49 ); 50 51 my $Return = sub { 52 my %LocalParam = @_; 53 $Param{CommunicationLogObject}->ObjectLogStop( 54 ObjectLogType => 'Connection', 55 Status => $LocalParam{Success} ? 'Successful' : 'Failed', 56 ); 57 58 return %LocalParam; 59 }; 60 61 my $ReturnSuccess = sub { return $Return->( @_, Success => 1, ); }; 62 my $ReturnError = sub { return $Return->( @_, Success => 0, ); }; 63 64 # get config object 65 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 66 67 # get config data 68 $Self->{FQDN} = $ConfigObject->Get('FQDN'); 69 $Self->{MailHost} = $ConfigObject->Get('SendmailModule::Host') 70 || die "No SendmailModule::Host found in Kernel/Config.pm"; 71 $Self->{SMTPPort} = $ConfigObject->Get('SendmailModule::Port'); 72 $Self->{User} = $ConfigObject->Get('SendmailModule::AuthUser'); 73 $Self->{Password} = $ConfigObject->Get('SendmailModule::AuthPassword'); 74 75 $Param{CommunicationLogObject}->ObjectLog( 76 ObjectLogType => 'Connection', 77 Priority => 'Debug', 78 Key => 'Kernel::System::Email::SMTP', 79 Value => 'Testing connection to SMTP service (3 attempts max.).', 80 ); 81 82 # 3 possible attempts to connect to the SMTP server. 83 # (MS Exchange Servers have sometimes problems on port 25) 84 my $SMTP; 85 86 my $TryConnectMessage = sprintf 87 "%%s: Trying to connect to '%s%s' on %s with SMTP type '%s'.", 88 $Self->{MailHost}, 89 ( $Self->{SMTPPort} ? ':' . $Self->{SMTPPort} : '' ), 90 $Self->{FQDN}, 91 $Self->{SMTPType}; 92 TRY: 93 for my $Try ( 1 .. 3 ) { 94 95 $Param{CommunicationLogObject}->ObjectLog( 96 ObjectLogType => 'Connection', 97 Priority => 'Debug', 98 Key => 'Kernel::System::Email::SMTP', 99 Value => sprintf( $TryConnectMessage, $Try, ), 100 ); 101 102 # connect to mail server 103 eval { 104 $SMTP = $Self->_Connect( 105 MailHost => $Self->{MailHost}, 106 FQDN => $Self->{FQDN}, 107 SMTPPort => $Self->{SMTPPort}, 108 SMTPDebug => $Self->{SMTPDebug}, 109 ); 110 return 1; 111 } || do { 112 my $Error = $@; 113 $Kernel::OM->Get('Kernel::System::Log')->Log( 114 Priority => 'error', 115 Message => sprintf( 116 "SMTP, connection try %s, unexpected error captured: %s", 117 $Try, 118 $Error, 119 ), 120 ); 121 }; 122 123 last TRY if $SMTP; 124 125 $Param{CommunicationLogObject}->ObjectLog( 126 ObjectLogType => 'Connection', 127 Priority => 'Debug', 128 Key => 'Kernel::System::Email::SMTP', 129 Value => "$Try: Connection could not be established. Waiting for 0.3 seconds.", 130 ); 131 132 # sleep 0,3 seconds; 133 select( undef, undef, undef, 0.3 ); ## no critic 134 } 135 136 # return if no connect was possible 137 if ( !$SMTP ) { 138 139 $Param{CommunicationLogObject}->ObjectLog( 140 ObjectLogType => 'Connection', 141 Priority => 'Error', 142 Key => 'Kernel::System::Email::SMTP', 143 Value => "Could not connect to host '$Self->{MailHost}'. ErrorMessage: $!", 144 ); 145 146 return $ReturnError->( 147 ErrorMessage => "Can't connect to $Self->{MailHost}: $!!", 148 ); 149 } 150 151 # Enclose SMTP in a wrapper to handle unexpected exceptions 152 $SMTP = $Self->_GetSMTPSafeWrapper( 153 SMTP => $SMTP, 154 ); 155 156 # use smtp auth if configured 157 if ( $Self->{User} && $Self->{Password} ) { 158 159 $Param{CommunicationLogObject}->ObjectLog( 160 ObjectLogType => 'Connection', 161 Priority => 'Debug', 162 Key => 'Kernel::System::Email::SMTP', 163 Value => "Using SMTP authentication with user '$Self->{User}' and (hidden) password.", 164 ); 165 166 if ( !$SMTP->( 'auth', $Self->{User}, $Self->{Password} ) ) { 167 168 my $Code = $SMTP->( 'code', ); 169 my $Error = $Code . ', ' . $SMTP->( 'message', ); 170 171 $SMTP->( 'quit', ); 172 173 $Param{CommunicationLogObject}->ObjectLog( 174 ObjectLogType => 'Connection', 175 Priority => 'Error', 176 Key => 'Kernel::System::Email::SMTP', 177 Value => "SMTP authentication failed (SMTP code: $Code, ErrorMessage: $Error).", 178 ); 179 180 return $ReturnError->( 181 ErrorMessage => "SMTP authentication failed: $Error!", 182 Code => $Code, 183 ); 184 } 185 } 186 187 return $ReturnSuccess->( 188 SMTP => $SMTP, 189 ); 190} 191 192sub Send { 193 my ( $Self, %Param ) = @_; 194 195 $Param{CommunicationLogObject}->ObjectLog( 196 ObjectLogType => 'Message', 197 Priority => 'Info', 198 Key => 'Kernel::System::Email::SMTP', 199 Value => 'Received message for sending, validating message contents.', 200 ); 201 202 # check needed stuff 203 for (qw(Header Body ToArray)) { 204 if ( !$Param{$_} ) { 205 206 $Param{CommunicationLogObject}->ObjectLog( 207 ObjectLogType => 'Message', 208 Priority => 'Error', 209 Key => 'Kernel::System::Email::SMTP', 210 Value => "Need $_!", 211 ); 212 213 return $Self->_SendError( 214 %Param, 215 ErrorMessage => "Need $_!", 216 ); 217 } 218 } 219 if ( !$Param{From} ) { 220 $Param{From} = ''; 221 } 222 223 # connect to smtp server 224 my %Result = $Self->Check(%Param); 225 226 if ( !$Result{Success} ) { 227 return $Self->_SendError( %Param, %Result, ); 228 } 229 230 # set/get SMTP handle 231 my $SMTP = $Result{SMTP}; 232 233 $Param{CommunicationLogObject}->ObjectLog( 234 ObjectLogType => 'Message', 235 Priority => 'Debug', 236 Key => 'Kernel::System::Email::SMTP', 237 Value => "Sending envelope from (mail from: $Param{From}) to server.", 238 ); 239 240 # set envelope from, return if from was not accepted by the server 241 if ( !$SMTP->( 'mail', $Param{From}, ) ) { 242 243 my $FullErrorMessage = sprintf( 244 "Envelope from '%s' not accepted by the server: %s, %s!", 245 $Param{From}, 246 $SMTP->( 'code', ), 247 $SMTP->( 'message', ), 248 ); 249 250 $Param{CommunicationLogObject}->ObjectLog( 251 ObjectLogType => 'Message', 252 Priority => 'Error', 253 Key => 'Kernel::System::Email::SMTP', 254 Value => $FullErrorMessage, 255 ); 256 257 return $Self->_SendError( 258 %Param, 259 ErrorMessage => $FullErrorMessage, 260 SMTP => $SMTP, 261 ); 262 } 263 264 TO: 265 for my $To ( @{ $Param{ToArray} } ) { 266 267 $Param{CommunicationLogObject}->ObjectLog( 268 ObjectLogType => 'Message', 269 Priority => 'Debug', 270 Key => 'Kernel::System::Email::SMTP', 271 Value => "Sending envelope to (rcpt to: $To) to server.", 272 ); 273 274 # Check if the recipient is valid 275 next TO if $SMTP->( 'to', $To, ); 276 277 my $FullErrorMessage = sprintf( 278 "Envelope to '%s' not accepted by the server: %s, %s!", 279 $To, 280 $SMTP->( 'code', ), 281 $SMTP->( 'message', ), 282 ); 283 284 $Param{CommunicationLogObject}->ObjectLog( 285 ObjectLogType => 'Message', 286 Priority => 'Error', 287 Key => 'Kernel::System::Email::SMTP', 288 Value => $FullErrorMessage, 289 ); 290 291 return $Self->_SendError( 292 %Param, 293 ErrorMessage => $FullErrorMessage, 294 SMTP => $SMTP, 295 ); 296 } 297 298 my $ToString = join ',', @{ $Param{ToArray} }; 299 300 # get encode object 301 my $EncodeObject = $Kernel::OM->Get('Kernel::System::Encode'); 302 303 # encode utf8 header strings (of course, there should only be 7 bit in there!) 304 $EncodeObject->EncodeOutput( $Param{Header} ); 305 306 # encode utf8 body strings 307 $EncodeObject->EncodeOutput( $Param{Body} ); 308 309 # send data 310 $Param{CommunicationLogObject}->ObjectLog( 311 ObjectLogType => 'Message', 312 Priority => 'Debug', 313 Key => 'Kernel::System::Email::SMTP', 314 Value => "Sending message data to server.", 315 ); 316 317 # Send email data by chunks because when in SSL mode, each SSL 318 # frame has a maximum of 16kB (Bug #12957). 319 # We send always the first 4000 characters until '$Data' is empty. 320 # If any error occur while sending data to the smtp server an exception 321 # is thrown and '$DataSent' will be undefined. 322 my $DataSent = eval { 323 my $Data = ${ $Param{Header} } . "\n" . ${ $Param{Body} }; 324 my $ChunkSize = 4000; 325 326 $SMTP->( 'data', ) || die "error starting data sending"; 327 328 while ( my $DataLength = length $Data ) { 329 my $TmpChunkSize = ( $ChunkSize > $DataLength ) ? $DataLength : $ChunkSize; 330 my $Chunk = substr $Data, 0, $TmpChunkSize; 331 332 $SMTP->( 'datasend', $Chunk, ) || die "error sending data chunk"; 333 334 $Data = substr $Data, $TmpChunkSize; 335 } 336 337 $SMTP->( 'dataend', ) || die "error ending data sending"; 338 339 return 1; 340 }; 341 342 if ( !$DataSent ) { 343 my $FullErrorMessage = sprintf( 344 "Could not send message to server: %s, %s!", 345 $SMTP->( 'code', ), 346 $SMTP->( 'message', ), 347 ); 348 349 $Param{CommunicationLogObject}->ObjectLog( 350 ObjectLogType => 'Message', 351 Priority => 'Error', 352 Key => 'Kernel::System::Email::SMTP', 353 Value => $FullErrorMessage, 354 ); 355 356 return $Self->_SendError( 357 %Param, 358 ErrorMessage => $FullErrorMessage, 359 SMTP => $SMTP, 360 ); 361 } 362 363 # debug 364 if ( $Self->{Debug} > 2 ) { 365 $Kernel::OM->Get('Kernel::System::Log')->Log( 366 Priority => 'notice', 367 Message => "Sent email to '$ToString' from '$Param{From}'.", 368 ); 369 } 370 371 $Param{CommunicationLogObject}->ObjectLog( 372 ObjectLogType => 'Message', 373 Priority => 'Info', 374 Key => 'Kernel::System::Email::SMTP', 375 Value => "Email successfully sent from '$Param{From}' to '$ToString'.", 376 ); 377 378 return $Self->_SendSuccess( 379 SMTP => $SMTP, 380 %Param 381 ); 382} 383 384sub _Connect { 385 my ( $Self, %Param ) = @_; 386 387 # check needed stuff 388 for (qw(MailHost FQDN)) { 389 if ( !$Param{$_} ) { 390 $Kernel::OM->Get('Kernel::System::Log')->Log( 391 Priority => 'error', 392 Message => "Need $_!", 393 ); 394 return; 395 } 396 } 397 398 # Remove a possible port from the FQDN value 399 my $FQDN = $Param{FQDN}; 400 $FQDN =~ s{:\d+}{}smx; 401 402 # set up connection connection 403 my $SMTP = Net::SMTP->new( 404 $Param{MailHost}, 405 Hello => $FQDN, 406 Port => $Param{SMTPPort} || 25, 407 Timeout => 30, 408 Debug => $Param{SMTPDebug}, 409 ); 410 411 return $SMTP; 412} 413 414sub _SendResult { 415 my ( $Self, %Param ) = @_; 416 417 my $SMTP = delete $Param{SMTP}; 418 $SMTP->( 'quit', ) if $SMTP; 419 420 return {%Param}; 421} 422 423sub _SendSuccess { 424 my ( $Self, %Param ) = @_; 425 return $Self->_SendResult( 426 Success => 1, 427 %Param 428 ); 429} 430 431sub _SendError { 432 my ( $Self, %Param ) = @_; 433 434 my $SMTP = $Param{SMTP}; 435 if ( $SMTP && !defined $Param{Code} ) { 436 $Param{Code} = $SMTP->( 'code', ); 437 } 438 439 return $Self->_SendResult( 440 Success => 0, 441 %Param, 442 SMTPError => 1, 443 ); 444} 445 446sub _GetSMTPSafeWrapper { 447 my ( $Self, %Param, ) = @_; 448 449 my $SMTP = $Param{SMTP}; 450 451 return sub { 452 my $Operation = shift; 453 my @LocalParams = @_; 454 455 my $ScalarResult; 456 my @ArrayResult = (); 457 my $Wantarray = wantarray; 458 459 eval { 460 if ($Wantarray) { 461 @ArrayResult = $SMTP->$Operation( @LocalParams, ); 462 } 463 else { 464 $ScalarResult = $SMTP->$Operation( @LocalParams, ); 465 } 466 467 return 1; 468 } || do { 469 my $Error = $@; 470 $Kernel::OM->Get('Kernel::System::Log')->Log( 471 Priority => 'error', 472 Message => sprintf( 473 "Error while executing 'SMTP->%s(%s)': %s", 474 $Operation, 475 join( ',', @LocalParams ), 476 $Error, 477 ), 478 ); 479 }; 480 481 return @ArrayResult if $Wantarray; 482 return $ScalarResult; 483 }; 484} 485 4861; 487