]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/Mail.pm
[project @ 2005-08-17 21:46:13 by don]
[debbugs.git] / Debbugs / Mail.pm
1 # $Id: Mail.pm,v 1.1 2005/08/17 21:46:16 don Exp $
2
3 package Debbugs::Mail;
4
5 =head1 NAME
6
7 Debbugs::Mail -- Outgoing Mail Handling
8
9 =head1 SYNOPSIS
10
11 use Debbugs::Mail qw(send_mail_message get_addresses);
12
13 my @addresses = get_addresses('blah blah blah foo@bar.com')
14 send_mail_message(message => <<END, recipients=>[@addresses]);
15 To: $addresses[0]
16 Subject: Testing
17
18 Testing 1 2 3
19 END
20
21 =head1 EXPORT TAGS
22
23 =over
24
25 =item :all -- all functions that can be exported
26
27 =back
28
29 =head1 FUNCTIONS
30
31
32 =cut
33
34 use warnings;
35 use strict;
36 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
37 use base qw(Exporter);
38
39 use IPC::Open3;
40 use POSIX ":sys_wait_h";
41 use Time::HiRes qw(usleep);
42 use Mail::Address ();
43 use Debbugs::MIME qw(encode_rfc1522);
44
45 BEGIN{
46      ($VERSION) = q$Revision: 1.1 $ =~ /^Revision:\s+([^\s+])/;
47      $DEBUG = 0 unless defined $DEBUG;
48
49      @EXPORT = ();
50      @EXPORT_OK = qw(send_mail_message get_addresses encode_headers);
51      $EXPORT_TAGS{all} = [@EXPORT_OK];
52
53 }
54
55 =head2 get_addresses
56
57      my @addresses = get_addresses('don@debian.org blars@debian.org
58                                     kamion@debian.org ajt@debian.org');
59
60 Given a string containing some e-mail addresses, parses the string
61 using Mail::Address->parse and returns a list of the addresses.
62
63 =cut
64
65 sub get_addresses {
66      return map { $_->address() } map { Mail::Address->parse($_) } @_;
67 }
68
69
70
71 =head2 send_mail_message
72
73      send_mail_message(message    => $message,
74                        recipients => [@recipients],
75                        envelope_from => 'don@debian.org',
76                       );
77
78
79 =over
80
81 =item message -- message to send out
82
83 =item recipients -- recipients to send the message to. If undefed or
84 an empty arrayref, will use '-t' to parse the message for recipients.
85
86 =item envelope_from -- envelope_from for outgoing messages
87
88 =item encode_headers -- encode headers using RFC1522 (default)
89
90 =item parse_for_recipients -- use -t to parse the message for
91 recipients in addition to those specified. [Can be used to set Bcc
92 recipients, for example.]
93
94 =back
95
96 Returns true on success, false on failures. All errors are indicated
97 using warn.
98
99 =cut
100
101 sub send_mail_message{
102      die "send_mail_message requires an even number of arguments" if @_ % 2;
103      # It would be better to use Param::Validate instead...
104      my %param = @_;
105
106      die "send_mail_message requires a message" if not defined $param{message};
107
108      my @sendmail_arguments = qw(-odq -oem -oi);
109      push @sendmail_arguments, '-f', $param{envelope_from} if exists $param{envelope_from};
110
111      my @recipients;
112      @recipients = @{$param{recipients}} if defined $param{recipients} and
113           ref($param{recipients}) eq 'ARRAY';
114      # If there are no recipients, use -t to parse the message
115      if (@recipients == 0) {
116           $param{parse_for_recipients} = 1 unless exists $param{parse_for_recipients};
117      }
118      # Encode headers if necessary
119      $param{encode_headers} = 1 if not exists $param{encode_headers};
120      if ($param{encode_headers}) {
121           $param{message} = encode_headers($param{message});
122      }
123
124      # First, try to send the message as is.
125      eval {
126           _send_message($param{message},
127                         @sendmail_arguments,
128                         $param{parse_for_recipients}?q(-t):(),
129                         @recipients);
130      };
131      return 1 unless $@;
132      # If there's only one recipient, there's nothing more we can do,
133      # so bail out.
134      warn $@ and return 0 if $@ and @recipients == 0;
135      # If that fails, try to send the message to each of the
136      # recipients separately. We also send the -t option separately in
137      # case one of the @recipients is ok, but the addresses in the
138      # mail message itself are malformed.
139      my @errors;
140      for my $recipient ($param{parse_for_recipients}?q(-t):(),@recipients) {
141           eval {
142                _send_message($param{message},@sendmail_arguments,$recipient);
143           };
144           push @errors, "Sending to $recipient failed with $@" if $@;
145      }
146      # If it still fails, complain bitterly but don't die.
147      warn join(qq(\n),@errors) and return 0 if @errors;
148      return 1;
149 }
150
151 =head2 encode_headers
152
153      $message = encode_heeaders($message);
154
155 RFC 1522 encodes the headers of a message
156
157 =cut
158
159 sub encode_headers{
160      my ($message) = @_;
161
162      my ($header,$body) = split /\n\n/, $message, 2;
163      $header = encode_rfc1522($header);
164      return $header . qq(\n\n). $body;
165 }
166
167
168 =head1 PRIVATE FUNCTIONS
169
170 =head2 _send_message
171
172      _send_message($message,@sendmail_args);
173
174 Private function that actually calls sendmail with @sendmail_args and
175 sends message $message.
176
177 dies with errors, so calls to this function in send_mail_message
178 should be wrapped in eval.
179
180 =cut
181
182 sub _send_message{
183      my ($message,@sendmail_args) = @_;
184
185      my ($wfh,$rfh);
186      my $pid = open3($wfh,$rfh,$rfh,'/usr/lib/sendmail',@sendmail_args)
187           or die "Unable to fork off /usr/lib/sendmail: $!";
188      local $SIG{PIPE} = 'IGNORE';
189      eval {
190           print {$wfh} $message or die "Unable to write to /usr/lib/sendmail: $!";
191           close $wfh or die "/usr/lib/sendmail exited with $?";
192      };
193      if ($@) {
194           local $\;
195           # Reap the zombie
196           waitpid($pid,WNOHANG);
197           # This shouldn't block because the pipe closing is the only
198           # way this should be triggered.
199           my $message = <$rfh>;
200           die "$@$message";
201      }
202      # Wait for sendmail to exit for at most 30 seconds.
203      my $loop = 0;
204      while (waitpid($pid, WNOHANG) == 0 or $loop++ >= 600){
205           # sleep for a 20th of a second
206           usleep(50_000);
207      }
208      if ($loop >= 600) {
209           warn "Sendmail didn't exit within 30 seconds";
210      }
211 }
212
213
214 1;
215
216
217 __END__
218
219
220
221
222
223