# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::Calendar::Event::Transport::Email;
## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
## nofilter(TidyAll::Plugin::OTRS::Perl::ParamObject)
use strict;
use warnings;
use Kernel::System::VariableCheck qw(:all);
use Kernel::Language qw(Translatable);
use parent qw(Kernel::System::Calendar::Event::Transport::Base);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Output::HTML::Layout',
'Kernel::System::Crypt::PGP',
'Kernel::System::Crypt::SMIME',
'Kernel::System::Email',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::SystemAddress',
'Kernel::System::Web::Request',
);
=head1 NAME
Kernel::System::Calendar::Event::Transport::Email - email transport layer for appointment notifications
=head1 DESCRIPTION
Notification event transport layer.
=head1 PUBLIC INTERFACE
=head2 new()
create a notification transport object. Do not use it directly, instead use:
my $TransportObject = $Kernel::OM->Get('Kernel::System::Ticket::Event::NotificationEvent::Transport::Email');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
return $Self;
}
sub SendNotification {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(UserID Notification Recipient)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need $Needed!',
);
return;
}
}
# cleanup event data
$Self->{EventData} = undef;
# get needed objects
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $SystemAddressObject = $Kernel::OM->Get('Kernel::System::SystemAddress');
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
# get recipient data
my %Recipient = %{ $Param{Recipient} };
return if !$Recipient{UserEmail};
return if $Recipient{UserEmail} !~ /@/;
my $IsLocalAddress = $Kernel::OM->Get('Kernel::System::SystemAddress')->SystemAddressIsLocalAddress(
Address => $Recipient{UserEmail},
);
return if $IsLocalAddress;
my %Notification = %{ $Param{Notification} };
if ( $Param{Notification}->{ContentType} && $Param{Notification}->{ContentType} eq 'text/html' ) {
# Get configured template with fallback to Default.
my $EmailTemplate = $Param{Notification}->{Data}->{TransportEmailTemplate}->[0] || 'Default';
my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home');
my $TemplateDir = "$Home/Kernel/Output/HTML/Templates/Standard/NotificationEvent/Email";
my $CustomTemplateDir = "$Home/Custom/Kernel/Output/HTML/Templates/Standard/NotificationEvent/Email";
if ( !-r "$TemplateDir/$EmailTemplate.tt" && !-r "$CustomTemplateDir/$EmailTemplate.tt" ) {
$EmailTemplate = 'Default';
}
# generate HTML
$Notification{Body} = $LayoutObject->Output(
TemplateFile => "NotificationEvent/Email/$EmailTemplate",
Data => {
Body => $Notification{Body},
Subject => $Notification{Subject},
},
);
}
my $FromEmail = $ConfigObject->Get('NotificationSenderEmail');
# send notification
my $From = $ConfigObject->Get('NotificationSenderName') . ' <'
. $FromEmail . '>';
# security part
my $SecurityOptions = $Self->SecurityOptionsGet( %Param, FromEmail => $FromEmail );
return if !$SecurityOptions;
# get needed objects
my $EmailObject = $Kernel::OM->Get('Kernel::System::Email');
my $Sent = $EmailObject->Send(
From => $From,
To => $Recipient{UserEmail},
Subject => $Notification{Subject},
MimeType => $Notification{ContentType},
Type => $Notification{ContentType},
Charset => 'utf-8',
Body => $Notification{Body},
Loop => 1,
Attachment => $Param{Attachments},
EmailSecurity => $SecurityOptions || {},
);
if ( !$Sent->{Success} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "'$Notification{Name}' notification could not be sent to agent '$Recipient{UserEmail} ",
);
return;
}
# log event
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'info',
Message => "Sent agent '$Notification{Name}' notification to '$Recipient{UserEmail}'.",
);
return 1;
}
sub GetTransportRecipients {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Notification)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed",
);
}
}
my @Recipients;
# get recipients by RecipientEmail
if ( $Param{Notification}->{Data}->{RecipientEmail} ) {
if ( $Param{Notification}->{Data}->{RecipientEmail}->[0] ) {
my $RecipientEmail = $Param{Notification}->{Data}->{RecipientEmail}->[0];
my @RecipientEmails;
if ( !IsArrayRefWithData($RecipientEmail) ) {
# Split multiple recipients on known delimiters: comma and semi-colon.
# Do this after the OTRS tags were replaced.
@RecipientEmails = split /[;,\s]+/, $RecipientEmail;
}
else {
@RecipientEmails = @{$RecipientEmail};
}
# Include only valid email recipients.
for my $Recipient ( sort @RecipientEmails ) {
if ( $Recipient && $Recipient =~ /@/ ) {
push @Recipients, {
Realname => '',
Type => 'Customer',
UserEmail => $Recipient,
IsVisibleForCustomer => $Param{Notification}->{Data}->{IsVisibleForCustomer},
};
}
}
}
}
return @Recipients;
}
sub TransportSettingsDisplayGet {
my ( $Self, %Param ) = @_;
KEY:
for my $Key (qw(RecipientEmail)) {
next KEY if !$Param{Data}->{$Key};
next KEY if !defined $Param{Data}->{$Key}->[0];
$Param{$Key} = $Param{Data}->{$Key}->[0];
}
my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home');
my $TemplateDir = "$Home/Kernel/Output/HTML/Templates/Standard/NotificationEvent/Email";
my $CustomTemplateDir = "$Home/Custom/Kernel/Output/HTML/Templates/Standard/NotificationEvent/Email";
my @Files = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => $TemplateDir,
Filter => '*.tt',
);
if ( -d $CustomTemplateDir ) {
push @Files, $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => $CustomTemplateDir,
Filter => '*.tt',
);
}
# for deduplication
my %Templates;
for my $File (@Files) {
$File =~ s{^.*/([^/]+)\.tt}{$1}smxg;
$Templates{$File} = $File;
}
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
$Param{TransportEmailTemplateStrg} = $LayoutObject->BuildSelection(
Data => \%Templates,
Name => 'TransportEmailTemplate',
Translation => 0,
SelectedID => $Param{Data}->{TransportEmailTemplate},
Class => 'Modernize W50pc',
);
# security fields
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my %SecuritySignEncryptOptions;
if ( $ConfigObject->Get('PGP') ) {
$SecuritySignEncryptOptions{'PGPSign'} = Translatable('PGP sign only');
$SecuritySignEncryptOptions{'PGPCrypt'} = Translatable('PGP encrypt only');
$SecuritySignEncryptOptions{'PGPSignCrypt'} = Translatable('PGP sign and encrypt');
}
if ( $ConfigObject->Get('SMIME') ) {
$SecuritySignEncryptOptions{'SMIMESign'} = Translatable('SMIME sign only');
$SecuritySignEncryptOptions{'SMIMECrypt'} = Translatable('SMIME encrypt only');
$SecuritySignEncryptOptions{'SMIMESignCrypt'} = Translatable('SMIME sign and encrypt');
}
# set security settings enabled
$Param{EmailSecuritySettings} = ( $Param{Data}->{EmailSecuritySettings} ? 'checked="checked"' : '' );
$Param{SecurityDisabled} = 0;
if ( $Param{EmailSecuritySettings} eq '' ) {
$Param{SecurityDisabled} = 1;
}
if ( !IsHashRefWithData( \%SecuritySignEncryptOptions ) ) {
$Param{EmailSecuritySettings} = 'disabled="disabled"';
$Param{EmailSecurityInfo} = Translatable('PGP and SMIME not enabled.');
}
# create security methods field
$Param{EmailSigningCrypting} = $LayoutObject->BuildSelection(
Data => \%SecuritySignEncryptOptions,
Name => 'EmailSigningCrypting',
SelectedID => $Param{Data}->{EmailSigningCrypting},
Class => 'Security Modernize W50pc',
Multiple => 0,
Translation => 1,
PossibleNone => 1,
Disabled => $Param{SecurityDisabled},
);
# create missing signing actions field
$Param{EmailMissingSigningKeys} = $LayoutObject->BuildSelection(
Data => [
{
Key => 'Skip',
Value => Translatable('Skip notification delivery'),
},
{
Key => 'Send',
Value => Translatable('Send unsigned notification'),
},
],
Name => 'EmailMissingSigningKeys',
SelectedID => $Param{Data}->{EmailMissingSigningKeys},
Class => 'Security Modernize W50pc',
Multiple => 0,
Translation => 1,
Disabled => $Param{SecurityDisabled},
);
# create missing crypting actions field
$Param{EmailMissingCryptingKeys} = $LayoutObject->BuildSelection(
Data => [
{
Key => 'Skip',
Value => Translatable('Skip notification delivery'),
},
{
Key => 'Send',
Value => Translatable('Send unencrypted notification'),
},
],
Name => 'EmailMissingCryptingKeys',
SelectedID => $Param{Data}->{EmailMissingCryptingKeys},
Class => 'Security Modernize W50pc',
Multiple => 0,
Translation => 1,
Disabled => $Param{SecurityDisabled},
);
# generate HTML
my $Output = $LayoutObject->Output(
TemplateFile => 'AdminAppointmentNotificationEventTransportEmailSettings',
Data => \%Param,
);
return $Output;
}
sub TransportParamSettingsGet {
my ( $Self, %Param ) = @_;
for my $Needed (qw(GetParam)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed",
);
}
}
# get param object
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
PARAMETER:
for my $Parameter (
qw(RecipientEmail TransportEmailTemplate
EmailSigningCrypting EmailMissingSigningKeys EmailMissingCryptingKeys
EmailSecuritySettings)
)
{
my @Data = $ParamObject->GetArray( Param => $Parameter );
next PARAMETER if !@Data;
$Param{GetParam}->{Data}->{$Parameter} = \@Data;
}
# Note: Example how to set errors and use them
# on the normal AdminNotificationEvent screen
# # set error
# $Param{GetParam}->{$Parameter.'ServerError'} = 'ServerError';
return 1;
}
sub IsUsable {
my ( $Self, %Param ) = @_;
# define if this transport is usable on
# this specific moment
return 1;
}
sub SecurityOptionsGet {
my ( $Self, %Param ) = @_;
# Verify security options are enabled.
my $EnableSecuritySettings = $Param{Notification}->{Data}->{EmailSecuritySettings}->[0] || '';
# Return empty hash ref to continue with email sending (without security options).
return {} if !$EnableSecuritySettings;
# Verify if the notification has to be signed or encrypted
my $SignEncryptNotification = $Param{Notification}->{Data}->{EmailSigningCrypting}->[0] || '';
# Return empty hash ref to continue with email sending (without security options).
return {} if !$SignEncryptNotification;
my %Queue = %{ $Param{Queue} || {} };
# Define who is going to be the sender (from the given parameters)
my $NotificationSenderEmail = $Param{FromEmail};
# Define security options container
my %SecurityOptions = (
Method => 'Detached',
);
my @SignKeys;
my @EncryptKeys;
my $KeyField;
# Get private and public keys for the given backend (PGP or SMIME)
if ( $SignEncryptNotification =~ /^PGP/i ) {
my $PGPObject = $Kernel::OM->Get('Kernel::System::Crypt::PGP');
if ( !$PGPObject ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No PGP support!",
);
return;
}
@SignKeys = $PGPObject->PrivateKeySearch(
Search => $NotificationSenderEmail,
);
# take just valid keys
@SignKeys = grep { $_->{Status} eq 'good' } @SignKeys;
# get public keys
@EncryptKeys = $PGPObject->PublicKeySearch(
Search => $Param{Recipient}->{UserEmail},
);
# Get PGP method (Detached or In-line).
if ( !$Kernel::OM->Get('Kernel::Output::HTML::Layout')->{BrowserRichText} ) {
$SecurityOptions{Method} = $Kernel::OM->Get('Kernel::Config')->Get('PGP::Method') || 'Detached';
}
$SecurityOptions{Backend} = 'PGP';
$KeyField = 'Key';
}
elsif ( $SignEncryptNotification =~ /^SMIME/i ) {
my $SMIMEObject = $Kernel::OM->Get('Kernel::System::Crypt::SMIME');
if ( !$SMIMEObject ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No SMIME support!",
);
return;
}
@SignKeys = $Kernel::OM->Get('Kernel::System::Crypt::SMIME')->PrivateSearch(
Search => $NotificationSenderEmail,
Valid => 1,
);
@EncryptKeys = $Kernel::OM->Get('Kernel::System::Crypt::SMIME')->CertificateSearch(
Search => $Param{Recipient}->{UserEmail},
Valid => 1,
);
$SecurityOptions{Backend} = 'SMIME';
$KeyField = 'Filename';
}
# Initialize sign key container
my %SignKey;
# Initialize crypt key container
my %EncryptKey;
# Get default signing key from the queue (if applies).
if ( $Queue{DefaultSignKey} ) {
my $DefaultSignKey;
# Convert legacy stored default sign keys.
if ( $Queue{DefaultSignKey} =~ m{ (?: Inline|Detached ) }msx ) {
my ( $Type, $SubType, $Key ) = split /::/, $Queue{DefaultSignKey};
$DefaultSignKey = $Key;
}
else {
my ( $Type, $Key ) = split /::/, $Queue{DefaultSignKey};
$DefaultSignKey = $Key;
}
if ( grep { $_->{$KeyField} eq $DefaultSignKey } @SignKeys ) {
$SignKey{$KeyField} = $DefaultSignKey;
}
}
# Otherwise take the first signing key available.
if ( !%SignKey ) {
SIGNKEY:
for my $SignKey (@SignKeys) {
%SignKey = %{$SignKey};
last SIGNKEY;
}
}
# Also take the first encryption key available.
CRYPTKEY:
for my $EncryptKey (@EncryptKeys) {
%EncryptKey = %{$EncryptKey};
last CRYPTKEY;
}
my $OnMissingSigningKeys = $Param{Notification}->{Data}->{EmailMissingSigningKeys}->[0] || '';
# Add options to sign the notification
if ( $SignEncryptNotification =~ /Sign/i ) {
# Take an action if there are missing signing keys.
if ( !IsHashRefWithData( \%SignKey ) ) {
my $Message
= "Could not sign notification '$Param{Notification}->{Name}' due to missing $SecurityOptions{Backend} sign key for '$NotificationSenderEmail'";
if ( $OnMissingSigningKeys eq 'Skip' ) {
# Log skipping notification (return nothing to stop email sending).
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => $Message . ', skipping notification distribution!',
);
return;
}
# Log sending unsigned notification.
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => $Message . ', sending unsigned!',
);
}
# Add signature option if a sign key is available
else {
$SecurityOptions{SignKey} = $SignKey{$KeyField};
}
}
my $OnMissingEncryptionKeys = $Param{Notification}->{Data}->{EmailMissingCryptingKeys}->[0] || '';
# Add options to encrypt the notification
if ( $SignEncryptNotification =~ /Crypt/i ) {
# Take an action if there are missing encryption keys.
if ( !IsHashRefWithData( \%EncryptKey ) ) {
my $Message
= "Could not encrypt notification '$Param{Notification}->{Name}' due to missing $SecurityOptions{Backend} encryption key for '$Param{Recipient}->{UserEmail}'";
if ( $OnMissingEncryptionKeys eq 'Skip' ) {
# Log skipping notification (return nothing to stop email sending).
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => $Message . ', skipping notification distribution!',
);
return;
}
# Log sending unencrypted notification.
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => $Message . ', sending unencrypted!',
);
}
# Add encrypt option if a encrypt key is available
else {
$SecurityOptions{EncryptKeys} = [ $EncryptKey{$KeyField}, ];
}
}
return \%SecurityOptions;
}
1;
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (GPL). If you
did not receive this file, see L.
=cut