File Coverage

blib/lib/Net/DCCIf.pm
Criterion Covered Total %
statement 18 99 18.1
branch 0 40 0.0
condition 0 13 0.0
subroutine 6 14 42.8
pod 8 8 100.0
total 32 174 18.3


line stmt bran cond sub pod time code
1             # $Id: DCCIf.pm,v 1.4 2004/02/11 14:36:48 matt Exp $
2              
3             package Net::DCCIf;
4 1     1   6779 use strict;
  1         3  
  1         36  
5              
6 1     1   791 use IO::Socket;
  1         33392  
  1         5  
7 1     1   493 use Socket qw(:crlf inet_ntoa);
  1         7  
  1         303  
8 1     1   1060 use Fatal qw(open close);
  1         17068  
  1         8  
9 1     1   1083 use Symbol qw(gensym);
  1         2  
  1         67  
10              
11 1     1   5 use vars qw($VERSION);
  1         1  
  1         1755  
12              
13             $VERSION = '0.02';
14              
15             my %result_map = (
16             A => 'Accept',
17             R => 'Reject',
18             S => 'Accept Some',
19             T => 'Temporary Failure',
20             );
21              
22             sub new {
23 0     0 1   my $class = shift;
24            
25 0           return bless {}, $class;
26             }
27              
28             sub connect {
29 0     0 1   my $self = shift;
30 0           my %opts = @_;
31            
32 0           %$self = (); # clear out self in case its being re-used.
33            
34 0   0       $opts{homedir} ||= $self->{homedir} || '/var/dcc';
      0        
35            
36             # this slightly odd logic copied from the original dccif.pl
37 0 0         if ($opts{clnt_addr}) {
    0          
38 0 0         inet_aton($opts{clnt_addr}) || die "Client address lookup failed: $!";
39             }
40             elsif ($opts{clnt_name}) {
41 0   0       $opts{clnt_addr} = inet_ntoa(scalar(gethostbyname($opts{clnt_name})))
42             || die "Cannot resolve domain name $opts{clnt_name}: $!";
43             }
44             else {
45 0           $opts{clnt_name} = '';
46             }
47            
48 0   0       my $server = IO::Socket::UNIX->new(
49             Type => SOCK_STREAM,
50             Peer => "$opts{homedir}/dccifd",
51             ) || die "Socket connect failed ($opts{homedir}/dccifd): $!";
52            
53 0           $self->{server} = $server;
54 0           $self->{homedir} = $opts{homedir};
55            
56 0           my @options;
57 0 0         if ($opts{known_spam}) {
58 0           push @options, "spam";
59             }
60 0 0         if ($opts{output_body}) {
61 0           push @options, "body";
62             }
63 0 0         if ($opts{output_header}) {
64 0           push @options, "header";
65             }
66 0 0         if ($opts{query_only}) {
67 0           push @options, "query";
68             }
69            
70 0           $self->send("opts", join(" ", @options), LF);
71            
72 0           $self->send("clnt helo env_from",
73             $opts{clnt_addr}, CR, $opts{clnt_name}, LF,
74             $opts{helo}, LF,
75             $opts{env_from}, LF,
76             );
77            
78 0 0         if (!ref($opts{env_to})) {
79 0 0         $opts{env_to} = $opts{env_to} ? [$opts{env_to}] : [];
80             }
81            
82 0           $self->{env_to} = $opts{env_to};
83            
84 0           foreach my $env_to (@{$opts{env_to}}) {
  0            
85 0           $self->send("env_to", $env_to, LF);
86             }
87            
88 0           $self->send("end of env_tos", LF);
89            
90 0           return $self;
91             }
92              
93             sub dcc_file {
94 0     0 1   my ($self, $file) = @_;
95            
96 0           my $fh = gensym();
97 0           open($fh, $file);
98            
99 0           return $self->dcc_fh($fh);
100             }
101              
102             sub dcc_fh {
103 0     0 1   my ($self, $fh) = @_;
104            
105 0           my $buf;
106 0           while (1) {
107 0           my $i = sysread($fh, $buf, 8192);
108 0 0         die "sysread file handle failed: $!" unless defined($i);
109 0 0         last unless $i;
110 0           $self->send("body", $buf);
111             }
112            
113 0           return $self->get_results();
114             }
115              
116             sub send {
117 0     0 1   my ($self, $type, @data) = @_;
118             # warn("send $type:", join('', @data)) if $type ne 'body';
119 0 0         $self->{server}->syswrite(join('', @data)) || die "socket write failed at $type: $!";
120             }
121              
122             sub get_results {
123 0     0 1   my ($self) = @_;
124            
125 0 0         if ($self->{results}) {
126 0           return @{$self->{results}};
  0            
127             }
128            
129 0 0         $self->{server}->shutdown(1) || die "socket shutdown failed: $!";
130 0   0       my $result = $self->{server}->getline || die "socket read failed: $!";
131 0   0       my $oks = $self->{server}->getline || die "socket read failed: $!";
132 0           chomp($result); chomp($oks);
  0            
133            
134 0           $result = $result_map{$result};
135 0           my @ok_map;
136 0           foreach my $env_to (@{$self->{env_to}}) {
  0            
137 0           my $val = substr($oks, 0, 1, '');
138 0           push @ok_map, $env_to, $result_map{$val};
139             }
140            
141 0           $self->{results} = [ $result, @ok_map ];
142 0           return( $result, @ok_map );
143             }
144              
145             sub get_output {
146 0     0 1   my ($self, %opts) = @_;
147            
148 0           my $output_fh = $opts{output_fh};
149 0 0         if (!$output_fh) {
150 0 0         if ($opts{output_file}) {
151 0           $output_fh = gensym();
152 0           open($output_fh, ">" . $self->{output_file});
153             }
154             }
155            
156 0           my $ret = '';
157 0           my $buf;
158 0           while (1) {
159 0           my $i = $self->{server}->read($buf, 8192);
160 0 0         die "read socket failed: $!" unless defined($i);
161 0 0         last unless $i;
162 0 0         if ($output_fh) {
163 0 0         print $output_fh ($buf) or die "write output filehandle failed: $!";
164             }
165             else {
166 0           $ret .= $buf;
167             }
168             }
169            
170 0           return $ret;
171             }
172              
173             sub disconnect {
174 0     0 1   my $self = shift;
175 0           delete $self->{server};
176             }
177              
178             1;
179              
180             =head1 NAME
181              
182             Net::DCCIf - Interface to the DCC daemon
183              
184             =head1 SYNOPSIS
185              
186             my $dcc = Net::DCCIf->new();
187             $dcc->connect();
188             my ($results, $oks) = $dcc->dcc_file("test.eml");
189             $dcc->disconnect();
190              
191             =head1 DESCRIPTION
192              
193             This module is a simple interface to the Distributed Checksum Clearinghouse
194             daemon (dccifd). It is a simpler replacement for the F script that
195             dcc ships with, making usage more perlish (though probably at the expense of
196             a slight performance drop).
197              
198             =head1 API
199              
200             The API is intentionally simple. Hopefully it allows enough flexibility to
201             support everything needed, however if not there may be some advantages to
202             sticking with F from the DCC distribution.
203              
204             =head2 C<< Net::DCCIf->new() >>
205              
206             This constructs a new Net::DCCIf object. It takes no options, and will always
207             return a valid object unless there is an out of memory error.
208              
209             =head2 C<< $dcc->connect(%options) >>
210              
211             Attempt to connect to the local unix domain socket. By default this domain
212             socket is expected to be at F, however you can override
213             this with the C option. If the connection fails for any reason
214             then an exception will be thrown detailing the error.
215              
216             Returns the object, to facilitate method chaining.
217              
218             B
219              
220             =over 4
221              
222             =item C<< env_from => $from >>
223              
224             The envelope from address (C data).
225              
226             =item C<< env_to => \@env_tos >>
227              
228             The envelope to addresses as an array reference (C data).
229              
230             B<< WARNING: >> if you pass an empty list here then DCC will assume
231             zero recipients and not increment the counter for this email (equivalent
232             to doing a C<< query_only >> lookup).
233              
234             =item C<< helo => $helo >>
235              
236             The HELO line.
237              
238             =item C<< homedir => $dir >>
239              
240             Specifies the location of the C unix domain socket.
241              
242             =item C<< clnt_addr => $addr >>
243              
244             Specifies the IP address of the connecting server. If this is an invalid
245             address then an exception will be thrown.
246              
247             =item C<< clnt_name => $name >>
248              
249             Specifies the host name of the connecting server. If the C is
250             specified, but C is not, then a hostname lookup will be
251             performed to try and determine the IP address. If this lookup fails an
252             exception will be thrown.
253              
254             =item C<< known_spam => 1 >>
255              
256             Specifies that we already know this email is spam (i.e. it came in to
257             a spamtrap address) and so we let the DCC server know about it.
258              
259             =item C<< output_body => 1 >>
260              
261             Makes L<< get_output()|/$dcc->get_output(%options) >> return the full body of the email with
262             a header added to it.
263              
264             =item C<< output_header => 1 >>
265              
266             Makes L<< get_output()|/$dcc->get_output(%options) >> return just a header line.
267              
268             =item C<< query_only => 1 >>
269              
270             Issues a query only, rather than first incrementing the database and then
271             querying.
272              
273             =back
274              
275             =head2 C<< $dcc->dcc_file($filename) >>
276              
277             Opens the file and calls L<< dcc_fh()|/$dcc->dcc_fh($fh) >> on the resulting filehandle.
278              
279             Returns C<($result, @mappings)>. See L below.
280              
281             =head2 C<< $dcc->dcc_fh($fh) >>
282              
283             Sends the contents of the filehandle to the dcc server.
284              
285             Returns C<($result, @mappings)>. See L below.
286              
287             =head2 C<< $dcc->send($type, @data) >>
288              
289             Sends raw text data to the dcc server. The type is usually one of C<"header"> or
290             C<"body">, and is used in error messages if there is a problem sending the
291             data.
292              
293             Use this method B any calls to C or C. Using it after
294             may result in an error or unexpected results.
295              
296             =head2 C<< $dcc->get_results() >>
297              
298             Following sending the email via C you have to manually extract the
299             results (these are the same results as returned by C and C
300             above).
301              
302             =head2 C<< $dcc->get_output(%options) >>
303              
304             This method returns the header or body from the dcc server that resulted from
305             running dcc on the data. The output depends on the values of the C
306             or C options passed in the C call.
307              
308             Returns the data as a string unless the C or C options
309             are set.
310              
311             B
312              
313             =over 4
314              
315             =item C<< output_fh => $fh >>
316              
317             A filehandle to send the output to. If you wish the output to go to STDOUT, you can
318             pass it with C<< $dcc->get_output(output_fh => \*STDOUT) >>.
319              
320             This option overrides any setting for C.
321              
322             =item C<< output_file => $file >>
323              
324             A filename to send the output to, as with C above.
325              
326             =back
327              
328             =head2 C<< $dcc->disconnect() >>
329              
330             Disconnect from the dccifd server and cleanup.
331              
332             =head1 Results
333              
334             The results returned from C, C and C above are a
335             list of values: C<($action, @mappings)>.
336              
337             The C<$action> value is one of:
338              
339             =over 4
340              
341             =item "Accept"
342              
343             =item "Reject"
344              
345             =item "Reject Some"
346              
347             =item "Temporary Failure"
348              
349             =back
350              
351             The C<@mappings> value is a list of envelope to addresses followed by the action
352             that should be taken for that address. It is often easier to map this to a hash:
353              
354             my ($action, %mappings) = $dcc->get_results();
355             print "Action: $action\n";
356             print "Matt Sergeant action: " . $mappings{'matt@sergeant.org'} . "\n";
357              
358             This should only have differing values in it should the primary action be
359             "Reject Some", otherwise the values will all be the same as C<$action>.
360              
361             Ordering of the mappings will be the same as the order of C addresses
362             passed to C above. Note that this ordering will be lost if you
363             map it to a hash.
364              
365             =head1 Exceptions
366              
367             This module throws exceptions for all errors. In order to catch these errors
368             without having your program exit you can use the C construct:
369              
370             my $dcc = Net::DCCIf->new();
371             eval {
372             $dcc->connect();
373             my ($results, %mapping) = $dcc->dcc_file("test.eml");
374             print "Results: $results\n";
375             print "Recipients: $_ => $mapping{$_}\n" for keys %mapping;
376             };
377             if ($@) {
378             warn("An error occurred in dcc: $@");
379             }
380              
381             =head1 BUGS
382              
383             No real test suite yet, as its hard to do when testing daemons and so I
384             got lazy :-(
385              
386             =head1 AUTHOR
387              
388             Matt Sergeant working for MessageLabs
389              
390             =head1 LICENSE
391              
392             This is free software. You may redistribute it under the same terms
393             as Perl itself.
394              
395             Copyright 2003. All Rights Reserved.
396              
397             =cut