Auftrags-Controller: Preisquellenermittlung in eigene Funktion ausgelagert
[kivitendo-erp.git] / SL / Mailer / SMTP.pm
index e2eff68..ce54397 100644 (file)
@@ -6,57 +6,100 @@ use parent qw(Rose::Object);
 
 use Rose::Object::MakeMethods::Generic
 (
-  scalar => [ qw(myconfig mailer form) ]
+  scalar => [ qw(myconfig mailer form status extended_status) ]
+);
+
+my %security_config = (
+  none => { require_module => 'Net::SMTP',          package => 'Net::SMTP',      port =>  25 },
+  tls  => { require_module => 'Net::SSLGlue::SMTP', package => 'Net::SMTP',      port =>  25 },
+  ssl  => { require_module => 'Net::SMTP::SSL',     package => 'Net::SMTP::SSL', port => 465 },
 );
 
 sub init {
   my ($self) = @_;
 
-  Rose::Object::init(@_);
+  Rose::Object::init(
+    @_,
+    status          => 'failed',
+    extended_status => 'no send attempt made',
+  );
 
   my $cfg           = $::lx_office_conf{mail_delivery} || {};
-  $self->{security} = lc($cfg->{security} || 'none');
+  $self->{security} = exists $security_config{lc $cfg->{security}} ? lc $cfg->{security} : 'none';
+  my $sec_cfg       = $security_config{ $self->{security} };
+
+  eval "require $sec_cfg->{require_module}" or do {
+    $self->extended_status("$@");
+    die $self->extended_status;
+  };
+
+  $self->{smtp} = $sec_cfg->{package}->new($cfg->{host} || 'localhost', Port => $cfg->{port} || $sec_cfg->{port});
+  if (!$self->{smtp}) {
+    $self->extended_status('SMTP connection could not be initialized');
+    die $self->extended_status;
+  }
 
   if ($self->{security} eq 'tls') {
-    require Net::SMTP::TLS;
-    my %params;
-    if ($cfg->{login}) {
-      $params{User}     = $cfg->{user};
-      $params{Password} = $cfg->{password};
-    }
-    $self->{smtp} = Net::SMTP::TLS->new($cfg->{host} || 'localhost', Port => $cfg->{port} || 25, %params);
-
-  } else {
-    my $module       = $self->{security} eq 'ssl' ? 'Net::SMTP::SSL' : 'Net::SMTP';
-    my $default_port = $self->{security} eq 'ssl' ? 465              : 25;
-    eval "require $module" or die $@;
-
-    $self->{smtp} = $module->new($cfg->{host} || 'localhost', Port => $cfg->{port} || $default_port);
-    $self->{smtp}->auth($cfg->{user}, $cfg->{password}) if $cfg->{login};
+    $self->{smtp}->starttls(SSL_verify_mode => 0) or do {
+      $self->extended_status("$@");
+      die $self->extended_status;
+    };
   }
 
-  die unless $self->{smtp};
+  # Backwards compatibility: older Versions used 'user' instead of the
+  # intended 'login'. Support both.
+  my $login = $cfg->{login} || $cfg->{user};
+
+  return 1 unless $login;
+
+  if (!$self->{smtp}->auth($login, $cfg->{password})) {
+    $self->extended_status('SMTP authentication failed');
+    die $self->extended_status;
+  }
 }
 
 sub start_mail {
   my ($self, %params) = @_;
 
-  $self->{smtp}->mail($params{from});
-  $self->{smtp}->recipient(@{ $params{to} });
-  $self->{smtp}->data;
+  $self->{smtp}->mail($params{from})         or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
+  $self->{smtp}->recipient(@{ $params{to} }) or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
+  $self->{smtp}->data                        or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
 }
 
 sub print {
   my $self = shift;
 
-  $self->{smtp}->datasend(@_);
+  # SMTP requires at most 1000 characters per line. Each line must be
+  # terminated with <CRLF>, meaning \r\n in Perl.
+
+  # First, normalize the string by removing all \r in order to fix
+  # possible wrong combinations like \n\r.
+  my $str = join '', @_;
+  $str    =~ s/\r//g;
+
+  # Now remove the very last newline so that we don't create a
+  # superfluous empty line at the very end.
+  $str =~ s/\n$//;
+
+  # Split the string on newlines keeping trailing empty parts. This is
+  # requires so that input like "Content-Disposition: ..... \n\n" is
+  # treated correctly. That's also why we had to remove the very last
+  # \n in the prior step.
+  my @lines = split /\n/, $str, -1;
+
+  # Send each line terminating it with \r\n.
+  $self->{smtp}->datasend("$_\r\n") for @lines;
 }
 
 sub send {
   my ($self) = @_;
 
-  $self->{smtp}->dataend;
+  my $ok = $self->{smtp}->dataend;
+  $self->extended_status($self->{smtp}->message);
+  $self->status('ok') if $ok;
+
   $self->{smtp}->quit;
+
   delete $self->{smtp};
 }