aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/Email
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2017-05-28 21:31:42 +0200
committerMarius Halden <marius.h@lden.org>2017-05-28 21:31:42 +0200
commit987124b09a32248414faf4d0d6615d43b29ac6f6 (patch)
treea549db8af723c981d3b346e855f25d6fd5ff8aa7 /perllib/Email
parentdbf56159e44c1560a413022451bf1a1c4cb22a52 (diff)
parenta085b63ce09f87e83b75cda9b9cd08aadfe75d61 (diff)
Merge tag 'v2.0.4' into fiksgatami-dev
Diffstat (limited to 'perllib/Email')
-rw-r--r--perllib/Email/Sender/Transport/SMTP.pm380
1 files changed, 380 insertions, 0 deletions
diff --git a/perllib/Email/Sender/Transport/SMTP.pm b/perllib/Email/Sender/Transport/SMTP.pm
new file mode 100644
index 000000000..c4eb6890c
--- /dev/null
+++ b/perllib/Email/Sender/Transport/SMTP.pm
@@ -0,0 +1,380 @@
+package Email::Sender::Transport::SMTP;
+# ABSTRACT: send email over SMTP
+$Email::Sender::Transport::SMTP::VERSION = '1.300030';
+use Moo;
+
+use Email::Sender::Failure::Multi;
+use Email::Sender::Success::Partial;
+use Email::Sender::Role::HasMessage ();
+use Email::Sender::Util;
+use MooX::Types::MooseLike::Base qw(Bool Int Str HashRef);
+use Net::SMTP 3.07; # SSL support, fixed datasend
+
+use utf8 (); # See below. -- rjbs, 2015-05-14
+use version ();
+
+#pod =head1 DESCRIPTION
+#pod
+#pod This transport is used to send email over SMTP, either with or without secure
+#pod sockets (SSL/TLS). It is one of the most complex transports available, capable
+#pod of partial success.
+#pod
+#pod For a potentially more efficient version of this transport, see
+#pod L<Email::Sender::Transport::SMTP::Persistent>.
+#pod
+#pod =head1 ATTRIBUTES
+#pod
+#pod The following attributes may be passed to the constructor:
+#pod
+#pod =over 4
+#pod
+#pod =item C<host>: the name of the host to connect to; defaults to C<localhost>
+#pod
+#pod =item C<ssl>: if 'starttls', use STARTTLS; if 'ssl' (or 1), connect securely;
+#pod otherwise, no security
+#pod
+#pod =item C<ssl_options>: passed to Net::SMTP constructor for 'ssl' connections or
+#pod to starttls for 'starttls' connections; should contain extra options for
+#pod IO::Socket::SSL
+#pod
+#pod =item C<port>: port to connect to; defaults to 25 for non-SSL, 465 for 'ssl',
+#pod 587 for 'starttls'
+#pod
+#pod =item C<timeout>: maximum time in secs to wait for server; default is 120
+#pod
+#pod =cut
+
+sub BUILD {
+ my ($self) = @_;
+ Carp::croak("do not pass port number to SMTP transport in host, use port parameter")
+ if $self->host =~ /:/;
+}
+
+has host => (is => 'ro', isa => Str, default => sub { 'localhost' });
+has ssl => (is => 'ro', isa => Str, default => sub { 0 });
+
+has _security => (
+ is => 'ro',
+ lazy => 1,
+ init_arg => undef,
+ default => sub {
+ my $ssl = $_[0]->ssl;
+ return '' unless $ssl;
+ $ssl = lc $ssl;
+ return 'starttls' if 'starttls' eq $ssl;
+ return 'ssl' if $ssl eq 1 or $ssl eq 'ssl';
+
+ Carp::cluck(qq{true "ssl" argument to Email::Sender::Transport::SMTP should be 'ssl' or 'startls' or '1' but got '$ssl'});
+
+ return 1;
+ },
+);
+
+has ssl_options => (is => 'ro', isa => HashRef, default => sub { {} });
+
+has port => (
+ is => 'ro',
+ isa => Int,
+ lazy => 1,
+ default => sub {
+ return $_[0]->_security eq 'starttls' ? 587
+ : $_[0]->_security eq 'ssl' ? 465
+ : 25
+ },
+);
+
+has timeout => (is => 'ro', isa => Int, default => sub { 120 });
+
+#pod =item C<sasl_username>: the username to use for auth; optional
+#pod
+#pod =item C<sasl_password>: the password to use for auth; required if C<username> is provided
+#pod
+#pod =item C<allow_partial_success>: if true, will send data even if some recipients were rejected; defaults to false
+#pod
+#pod =cut
+
+has sasl_username => (is => 'ro', isa => Str);
+has sasl_password => (is => 'ro', isa => Str);
+
+has allow_partial_success => (is => 'ro', isa => Bool, default => sub { 0 });
+
+#pod =item C<helo>: what to say when saying HELO; no default
+#pod
+#pod =item C<localaddr>: local address from which to connect
+#pod
+#pod =item C<localport>: local port from which to connect
+#pod
+#pod =cut
+
+has helo => (is => 'ro', isa => Str);
+has localaddr => (is => 'ro');
+has localport => (is => 'ro', isa => Int);
+
+#pod =item C<debug>: if true, put the L<Net::SMTP> object in debug mode
+#pod
+#pod =back
+#pod
+#pod =cut
+
+has debug => (is => 'ro', isa => Bool, default => sub { 0 });
+
+# I am basically -sure- that this is wrong, but sending hundreds of millions of
+# messages has shown that it is right enough. I will try to make it textbook
+# later. -- rjbs, 2008-12-05
+sub _quoteaddr {
+ my $addr = shift;
+ my @localparts = split /\@/, $addr;
+ my $domain = pop @localparts;
+ my $localpart = join q{@}, @localparts;
+
+ return $addr # The first regex here is RFC 821 "specials" excepting dot.
+ unless $localpart =~ /[\x00-\x1F\x7F<>\(\)\[\]\\,;:@"]/
+ or $localpart =~ /^\./
+ or $localpart =~ /\.$/;
+ return join q{@}, qq("$localpart"), $domain;
+}
+
+sub _smtp_client {
+ my ($self) = @_;
+
+ my $class = "Net::SMTP";
+
+ my $smtp = $class->new( $self->_net_smtp_args );
+
+ unless ($smtp) {
+ $self->_throw(
+ sprintf "unable to establish SMTP connection to %s port %s",
+ $self->host,
+ $self->port,
+ );
+ }
+
+ if ($self->_security eq 'starttls') {
+ $self->_throw("can't STARTTLS: " . $smtp->message)
+ unless $smtp->starttls(%{ $self->ssl_options });
+ }
+
+ if ($self->sasl_username) {
+ $self->_throw("sasl_username but no sasl_password")
+ unless defined $self->sasl_password;
+
+ unless ($smtp->auth($self->sasl_username, $self->sasl_password)) {
+ if ($smtp->message =~ /MIME::Base64|Authen::SASL/) {
+ Carp::confess("SMTP auth requires MIME::Base64 and Authen::SASL");
+ }
+
+ $self->_throw('failed AUTH', $smtp);
+ }
+ }
+
+ return $smtp;
+}
+
+sub _net_smtp_args {
+ my ($self) = @_;
+
+ return (
+ $self->host,
+ Port => $self->port,
+ Timeout => $self->timeout,
+ Debug => $self->debug,
+
+ (($self->_security eq 'ssl')
+ ? (SSL => 1, %{ $self->ssl_options })
+ : ()),
+
+ defined $self->helo ? (Hello => $self->helo) : (),
+ defined $self->localaddr ? (LocalAddr => $self->localaddr) : (),
+ defined $self->localport ? (LocalPort => $self->localport) : (),
+ );
+}
+
+sub _throw {
+ my ($self, @rest) = @_;
+ Email::Sender::Util->_failure(@rest)->throw;
+}
+
+sub send_email {
+ my ($self, $email, $env) = @_;
+
+ Email::Sender::Failure->throw("no valid addresses in recipient list")
+ unless my @to = grep { defined and length } @{ $env->{to} };
+
+ my $smtp = $self->_smtp_client;
+
+ my $FAULT = sub { $self->_throw($_[0], $smtp); };
+
+ $smtp->mail(_quoteaddr($env->{from}))
+ or $FAULT->("$env->{from} failed after MAIL FROM");
+
+ my @failures;
+ my @ok_rcpts;
+
+ for my $addr (@to) {
+ if ($smtp->to(_quoteaddr($addr))) {
+ push @ok_rcpts, $addr;
+ } else {
+ # my ($self, $error, $smtp, $error_class, @rest) = @_;
+ push @failures, Email::Sender::Util->_failure(
+ undef,
+ $smtp,
+ recipients => [ $addr ],
+ );
+ }
+ }
+
+ # This logic used to include: or (@ok_rcpts == 1 and $ok_rcpts[0] eq '0')
+ # because if called without SkipBad, $smtp->to can return 1 or 0. This
+ # should not happen because we now always pass SkipBad and do the counting
+ # ourselves. Still, I've put this comment here (a) in memory of the
+ # suffering it caused to have to find that problem and (b) in case the
+ # original problem is more insidious than I thought! -- rjbs, 2008-12-05
+
+ if (
+ @failures
+ and ((@ok_rcpts == 0) or (! $self->allow_partial_success))
+ ) {
+ $failures[0]->throw if @failures == 1;
+
+ my $message = sprintf '%s recipients were rejected during RCPT',
+ @ok_rcpts ? 'some' : 'all';
+
+ Email::Sender::Failure::Multi->throw(
+ message => $message,
+ failures => \@failures,
+ );
+ }
+
+ # restore Pobox's support for streaming, code-based messages, and arrays here
+ # -- rjbs, 2008-12-04
+
+ $smtp->data or $FAULT->("error at DATA start");
+
+ my $msg_string = $email->as_string;
+ my $hunk_size = $self->_hunk_size;
+
+ while (length $msg_string) {
+ my $next_hunk = substr $msg_string, 0, $hunk_size, '';
+ $smtp->datasend($next_hunk) or $FAULT->("error at during DATA");
+ }
+
+ $smtp->dataend or $FAULT->("error at after DATA");
+
+ my $message = $smtp->message;
+
+ $self->_message_complete($smtp);
+
+ # We must report partial success (failures) if applicable.
+ return $self->success({ message => $message }) unless @failures;
+ return $self->partial_success({
+ message => $message,
+ failure => Email::Sender::Failure::Multi->new({
+ message => 'some recipients were rejected during RCPT',
+ failures => \@failures
+ }),
+ });
+}
+
+sub _hunk_size { 2**20 } # send messages to DATA in hunks of 1 mebibyte
+
+sub success {
+ my $self = shift;
+ my $success = Moo::Role->create_class_with_roles('Email::Sender::Success', 'Email::Sender::Role::HasMessage')->new(@_);
+}
+
+sub partial_success {
+ my $self = shift;
+ my $partial_success = Moo::Role->create_class_with_roles('Email::Sender::Success::Partial', 'Email::Sender::Role::HasMessage')->new(@_);
+}
+
+sub _message_complete { $_[1]->quit; }
+
+#pod =head1 PARTIAL SUCCESS
+#pod
+#pod If C<allow_partial_success> was set when creating the transport, the transport
+#pod may return L<Email::Sender::Success::Partial> objects. Consult that module's
+#pod documentation.
+#pod
+#pod =cut
+
+with 'Email::Sender::Transport';
+no Moo;
+1;
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Email::Sender::Transport::SMTP - send email over SMTP
+
+=head1 VERSION
+
+version 1.300030
+
+=head1 DESCRIPTION
+
+This transport is used to send email over SMTP, either with or without secure
+sockets (SSL/TLS). It is one of the most complex transports available, capable
+of partial success.
+
+For a potentially more efficient version of this transport, see
+L<Email::Sender::Transport::SMTP::Persistent>.
+
+=head1 ATTRIBUTES
+
+The following attributes may be passed to the constructor:
+
+=over 4
+
+=item C<host>: the name of the host to connect to; defaults to C<localhost>
+
+=item C<ssl>: if 'starttls', use STARTTLS; if 'ssl' (or 1), connect securely;
+otherwise, no security
+
+=item C<ssl_options>: passed to Net::SMTP constructor for 'ssl' connections or
+to starttls for 'starttls' connections; should contain extra options for
+IO::Socket::SSL
+
+=item C<port>: port to connect to; defaults to 25 for non-SSL, 465 for 'ssl',
+587 for 'starttls'
+
+=item C<timeout>: maximum time in secs to wait for server; default is 120
+
+=item C<sasl_username>: the username to use for auth; optional
+
+=item C<sasl_password>: the password to use for auth; required if C<username> is provided
+
+=item C<allow_partial_success>: if true, will send data even if some recipients were rejected; defaults to false
+
+=item C<helo>: what to say when saying HELO; no default
+
+=item C<localaddr>: local address from which to connect
+
+=item C<localport>: local port from which to connect
+
+=item C<debug>: if true, put the L<Net::SMTP> object in debug mode
+
+=back
+
+=head1 PARTIAL SUCCESS
+
+If C<allow_partial_success> was set when creating the transport, the transport
+may return L<Email::Sender::Success::Partial> objects. Consult that module's
+documentation.
+
+=head1 AUTHOR
+
+Ricardo Signes <rjbs@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2016 by Ricardo Signes.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut