File Coverage

blib/lib/Mail/Qmail/Queue/Send.pm
Criterion Covered Total %
statement 95 102 93.1
branch 35 72 48.6
condition 1 2 50.0
subroutine 20 22 90.9
pod 13 13 100.0
total 164 211 77.7


line stmt bran cond sub pod time code
1             package Mail::Qmail::Queue::Send;
2             our $VERSION = 0.02;
3             #
4             # Copyright 2006 Scott Gifford
5             #
6             # This library is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8              
9 5     5   71350 use warnings;
  5         14  
  5         193  
10 5     5   26 use strict;
  5         9  
  5         187  
11              
12 5     5   25 use constant DEFAULT_QMAIL_QUEUE => '/var/qmail/bin/qmail-queue';
  5         9  
  5         327  
13 5     5   26 use constant QQ_ENV_FD => 1;
  5         7  
  5         328  
14 5     5   23 use constant QQ_BODY_FD => 0;
  5         10  
  5         286  
15              
16             =head1 NAME
17              
18             Mail::Qmail::Queue::Send - Send a message to a program implementing the qmail-queue interface
19              
20             =head1 SYNOPSIS
21              
22             use Mail::Qmail::Queue::Send;
23              
24             my $qq = Mail::Qmail::Queue::Send->new()
25             or die "Couldn't create qmail-queue sender: $!\n";
26              
27             $qq_send->body("Test message\n")
28             or die "Couldn't write body: $!\n";
29              
30             $qq_send->from('sgifford@suspectclass.com')
31             or die "Couldn't write envelope from: $!\n";
32             $qq_send->to('GIFF@cpan.org')
33             or die "Couldn't write envelope to #1: $!\n";
34             $qq_send->to('gifford@umich.edu')
35             or die "Couldn't write envelope to #2: $!\n";
36             $qq_send->envelope_done()
37             or die "Couldn't finish writing envelope: $!\n";
38              
39             $qq_send->wait_exitstatus() == 0
40             or die "Error sending message: exit status $?\n";
41              
42             Note that the L specifications require that the body
43             be read first, then the envelope.
44              
45             =cut
46              
47 5     5   9630 use POSIX;
  5         64175  
  5         37  
48 5     5   35183 use FileHandle;
  5         34125  
  5         56  
49              
50 5     5   5615 use Mail::Qmail::Queue::Error qw(:errcodes :fail);
  5         14  
  5         10449  
51              
52             =head1 DESCRIPTION
53              
54             This module sends a message to a program implementing the
55             L protocol. You must send the body first,
56             then the envelope.
57              
58             =head2 CONSTRUCTOR
59              
60             =over 4
61              
62             =item new ( %options )
63              
64             Creates a new qmail-queue sender. Executes C or the
65             equivalent, and sets up the file descriptors to prepare to talk to it.
66             If a C option is given, that will be used as the path to
67             the C program. Otherwise, the contents of the
68             environment variable C will be used; if that is unset,
69             C is the default.
70              
71             Available options are:
72              
73             =over 4
74              
75             =item QmailQueue
76              
77             Specifies the path to the program that will handle the message.
78              
79             =item LeaveEnvHandle
80              
81             Do not open up file descriptor 1 to C's envelope reader;
82             instead the current process's file descriptor 1 will be connected to
83             it. This is useful if you are writing a filter to change the body,
84             but want to leave the envelope alone.
85              
86             =item LeaveBodyHandle
87              
88             Do not open up file descriptor 0 to C's body reader;
89             instead the current process's file descriptor 0 will be connected to
90             it. This is useful if you are writing a filter to change the
91             envelope, but want to leave the body alone.
92              
93             =back
94              
95             =back
96              
97             =cut
98              
99             sub new
100             {
101 4     4 1 622 my $class = shift;
102 4         18 my %o = @_;
103 4         16 my $self = bless {}, $class;
104            
105 4   50     36 my $qq_path = $o{QmailQueue} || $ENV{QMAILQUEUE} || DEFAULT_QMAIL_QUEUE;
106 4 50       22 $self->{_qq_pid} = $self->_start_qmail_queue($qq_path,\%o)
107             or tempfail QQ_EXIT_NETFAIL, "Couldn't start up '$qq_path': $!\n";
108 2         141 $self;
109             }
110              
111             =head2 METHODS
112              
113             =over 4
114              
115             =item send ( $body, $from, @to )
116              
117             Sends a complete message, and returns the exit status of the
118             C program.
119              
120             =cut
121              
122             sub send
123             {
124 1     1 1 3 my $self = shift;
125 1 50       11 $self->body(shift)
126             or return undef;
127 1 50       11 $self->from(shift)
128             or return undef;
129 1 50       5 $self->to(@_)
130             or return undef;
131 1 50       5 $self->envelope_done
132             or return undef;
133 1         4 return $self->wait_exitstatus;
134             }
135              
136              
137             =item body_fh ( )
138              
139             Retrieves a Perl filehandle to which the message body can be written.
140              
141             =cut
142              
143             sub body_fh
144             {
145 0     0 1 0 my $self = shift;
146 0         0 return $self->{_msg_fh};
147             }
148              
149             =item body_close ( )
150              
151             Close the body filehandle. You must use this when you're done sending
152             the body filehandle, to indicate to the C program that
153             you're done, and to tell this module that it's ready to accept the
154             envelope.
155              
156             =cut
157              
158             sub body_close
159             {
160 2     2 1 6 my $self = shift;
161              
162 2         19 $self->{_body_sent}=1;
163 2         58 return close($self->{_msg_fh});
164             }
165              
166             =item body ( @body )
167              
168             Send the provided string or strings as the complete body of the
169             message, closing the filehandle after sending it.
170              
171             If you are working with a very large message, it may be more efficient
172             to write the body in smaller pieces to the filehandle returned by
173             C.
174              
175             =cut
176              
177             sub body
178             {
179 2     2 1 58 my $self = shift;
180 2 50       30 my $fh = $self->{_msg_fh}
181             or tempfail QQ_EXIT_BUG,"Body filehandle not available!";
182 2 50       82 print $fh @_
183             or tempfail QQ_EXIT_WRITEERR,"Write error: $!\n";
184 2 50       9 $self->body_close()
185             or tempfail QQ_EXIT_WRITEERR,"Error closing write pipe: $!\n";
186 2         7 return 1;
187             }
188              
189             =item from ( $from )
190              
191             Send the provided email address as the envelope from. You must send
192             the body first.
193              
194             =cut
195              
196             sub from
197             {
198 2     2 1 16 my $self = shift;
199              
200 2         11 my($from)=@_;
201 2         24 $self->{_from}=$from;
202 2         20 $self->put_envelope_entry('F'.$from);
203             }
204              
205             =item to ( @to )
206              
207             Send the provided email address or addresses as the envelope to. You
208             must send the body and the envelope from first.
209              
210             =cut
211              
212             sub to
213             {
214 3     3 1 23 my $self = shift;
215 3 50       10 defined($self->{_from})
216             or tempfail QQ_EXIT_BUG,"envelope from must be set before envelope to\n";
217 3         12 $self->put_envelope_entry(map { 'T'.$_ } @_);
  5         19  
218             }
219              
220             =item envelope_done ( )
221              
222             Indicate that you have sent all of the envelope, and are now done.
223             The filehandle will be closed, and C will probably begin
224             processing the message.
225              
226             =cut
227              
228             sub envelope_done
229             {
230 2     2 1 7 my $self = shift;
231 2 50       6 $self->put_envelope_entry('')
232             or return undef;
233 2 50       9 my $fh = $self->{_env_fh}
234             or tempfail QQ_EXIT_BUG, "No envelope filehandle\n";
235 2 50       24 close($fh)
236             or tempfail QQ_EXIT_WRITEERR,"Error closing envelope filehandle: $!\n";
237 2         6 return 1;
238             }
239              
240             =item put_envelope_entry ( @entries )
241              
242             Send the provided envelope entries. They must be properly formatted
243             entries, or else they will confuse the called C program.
244             The null character will be inserted between the entries by this
245             method, and you should not set it.
246              
247             Note that if you use this method instead of C, you cannot use
248             the C method, because this module won't know that you've already
249             sent an envelope from.
250              
251             L will
252             return strings that can be passed to this method.
253              
254             =cut
255              
256             sub put_envelope_entry
257             {
258 7     7 1 11 my $self = shift;
259              
260 7         25 $self->envelope_write(join("\0",@_)."\0");
261             }
262              
263             =item envelope_write ( @str )
264              
265             Send the provided string or strings directly to the C
266             envelope filehandle. This requires a knowledge of the
267             L protocol.
268              
269             =cut
270              
271             sub envelope_write
272             {
273 7     7 1 10 my $self = shift;
274 7 50       19 my $fh = $self->{_env_fh}
275             or tempfail QQ_EXIT_BUG, "No envelope filehandle\n";
276 7         82 return print $fh @_;
277             }
278              
279             =item envelope_fh
280              
281             Retrieve a Perl filehandle to which the message envelope can be
282             written. Using this filehandle requires knowledge of the envelope
283             format; see L for details.
284              
285             =cut
286              
287             sub envelope_fh
288             {
289 0     0 1 0 my $self = shift;
290 0         0 return $self->{_env_fh};
291             }
292              
293              
294             =item wait_exitstatus ( )
295              
296             Wait for the C program to finish, and return its exit
297             status. If the program is killed by a signal, L
298             will be returned.
299              
300             =cut
301              
302             sub wait_exitstatus
303             {
304 2     2 1 15 my $self = shift;
305              
306 2         12 $self->wait;
307 2 50       78 if ($? == 0)
    0          
308             {
309 2         64 return 0;
310             }
311             elsif ($? >> 8)
312             {
313 0         0 return $? >> 8;
314             }
315             else
316             {
317 0         0 return QQ_EXIT_BUG;
318             }
319             }
320              
321             =item wait ( )
322              
323             Wait for the C program to finish, and return the value
324             from C.
325              
326             =cut
327              
328             sub wait
329             {
330 2     2 1 9 my $self = shift;
331 2         1289063 waitpid($self->{_qq_pid},@_);
332             }
333              
334             sub _start_qmail_queue
335             {
336 4     4   26 my $self = shift;
337 4         14 my($path,$o)=@_;
338 4         6 my(@env_pipe,@body_pipe);
339            
340 4 50       24 unless ($o->{LeaveEnvHandle})
341             {
342 4 50       120 @env_pipe = POSIX::pipe()
343             or tempfail QQ_EXIT_WRITEERR, "Couldn't create envelope pipe: $!\n";
344             }
345 4 50       22 unless ($o->{LeaveMsgHandle})
346             {
347 4 50       50 @body_pipe = POSIX::pipe()
348             or tempfail QQ_EXIT_WRITEERR, "Couldn't create envelope pipe: $!\n";
349             }
350            
351 4         4335 my $f = fork();
352              
353 4 50       428 if (!defined($f))
    100          
354             {
355 0         0 tempfail QQ_EXIT_TEMPREFUSE, "Fork failed: $!\n";
356             }
357             elsif ($f)
358             {
359             # Parent
360 2 50       49 if (@body_pipe)
361             {
362 2 50       101 POSIX::close($body_pipe[0])
363             or tempfail QQ_EXIT_READERR, "Couldn't close body pipe reader in parent: $!\n";
364 2 50       190 $self->{_msg_fh} = FileHandle->new_from_fd($body_pipe[1],"w")
365             or tempfail QQ_EXIT_READERR, "Couldn't create FileHandle for body writer fd $body_pipe[1] in parent: $!\n";
366             }
367 2 50       1112 if (@env_pipe)
368             {
369 2 50       27 POSIX::close($env_pipe[0])
370             or tempfail QQ_EXIT_READERR, "Couldn't close envelope pipe reader in parent: $!\n";
371 2 50       30 $self->{_env_fh} = FileHandle->new_from_fd($env_pipe[1],"w")
372             or tempfail QQ_EXIT_READERR, "Couldn't create FileHandle for envelope pipe writer fd $env_pipe[1] in parent: $!\n";
373             }
374 2         11987 return $f;
375             }
376             else
377             {
378             # Child
379 2 50       55 if (@body_pipe)
380             {
381 2 50       145 POSIX::close($body_pipe[1])
382             or tempfail QQ_EXIT_WRITEERR, "Couldn't close body pipe writer in child: $!\n";
383 2         46 POSIX::close(QQ_BODY_FD); # Ignore errors
384 2 50       47 POSIX::dup2($body_pipe[0],QQ_BODY_FD)
385             or tempfail QQ_EXIT_WRITEERR, "Couldn't dup body pipe reader to fd 1 in child: $!\n";
386 2 50       45 POSIX::close($body_pipe[0])
387             or tempfail QQ_EXIT_WRITEERR, "Couldn't close body pipe reader after dup in child: $!\n";
388             }
389 2 50       140 if (@env_pipe)
390             {
391 2 50       34 POSIX::close($env_pipe[1])
392             or tempfail QQ_EXIT_WRITEERR, "Couldn't close envelope pipe writer in child: $!\n";
393 2         42 POSIX::close(QQ_ENV_FD); # Ignore errors
394 2 50       31 POSIX::dup2($env_pipe[0],QQ_ENV_FD)
395             or tempfail QQ_EXIT_WRITEERR, "Couldn't dup envelope pipe reader to fd 0 in child: $!\n";
396 2 50       63 POSIX::close($env_pipe[0])
397             or tempfail QQ_EXIT_WRITEERR, "Couldn't close envelope pipe reader after dup in child: $!\n";
398             }
399 2 0         exec($path)
400             or tempfail QQ_EXIT_TEMPREFUSE, "exec failed: $!\n";
401             }
402             }
403              
404             =back
405              
406             =head1 SEE ALSO
407              
408             L, L,
409             L, L.
410              
411             =head1 COPYRIGHT
412              
413             Copyright 2006 Scott Gifford.
414              
415             This library is free software; you can redistribute it and/or
416             modify it under the same terms as Perl itself.
417              
418             =cut
419              
420             1;