File Coverage

blib/lib/Net/QMTP.pm
Criterion Covered Total %
statement 71 218 32.5
branch 34 202 16.8
condition 1 9 11.1
subroutine 13 24 54.1
pod 12 12 100.0
total 131 465 28.1


line stmt bran cond sub pod time code
1             package Net::QMTP;
2              
3             require 5.001;
4 1     1   4636 use strict;
  1         2  
  1         37  
5              
6 1     1   868 use IO::Socket;
  1         55752  
  1         7  
7 1     1   814 use Carp;
  1         8  
  1         77  
8 1         92 use Text::Netstring qw(netstring_encode netstring_decode netstring_read
9 1     1   1117 netstring_verify);
  1         960  
10              
11             #
12             # Copyright (c) 2003 James Raftery . All rights reserved.
13             # This program is free software; you can redistribute it and/or
14             # modify it under the same terms as Perl itself.
15             # Please submit bug reports, patches and comments to the author.
16             # Latest information at http://romana.now.ie/#net-qmtp
17             #
18             # $Id: QMTP.pm,v 1.22 2004/11/02 14:56:18 james Exp $
19             #
20             # This module is an object interface to the Quick Mail Transfer Protocol
21             # (QMTP). QMTP is a replacement for the Simple Mail Transfer Protocol
22             # (SMTP). It offers increased speed, especially over high latency
23             # links, pipelining, 8-bit data transmission and predeclaration of
24             # line-ending encoding.
25             #
26             # See the Net::QMTP man page that was installed with this module for
27             # information on how to use the module. You require version 0.04 or
28             # later of the Text::Netstring module.
29             #
30              
31 1     1   6 use vars qw($VERSION);
  1         2  
  1         2939  
32             $VERSION = "0.06";
33              
34             sub new {
35 1 50   1 1 555 my $proto = shift or croak;
36 1   33     9 my $class = ref($proto) || $proto;
37 1 50       5 my $server = shift or croak "No server specified in constructor";
38 1         2 my %args;
39              
40 1 50       8 %args = @_ if @_;
41 1         16 my $self = {
42             SENDER => undef,
43             RECIPIENTS => [],
44             MESSAGE => undef,
45             MSGFILE => undef,
46             ENCODING => undef,
47             SOCKET => undef,
48             SERVER => undef,
49             PORT => 209,
50             DEBUG => undef,
51             TIMEOUT => undef, # use IO::Socket default
52             CONNECTTIME => undef,
53             SESSIONLIMIT => 3600, # 1 hour
54             };
55 1 50       5 if ($args{'Debug'}) {
56 0         0 $self->{DEBUG} = 1;
57 0         0 warn "debugging on; Version: $VERSION; RCS " .
58             qq$Revision: 1.22 $ . "\n";
59             }
60 1 50       4 $self->{SERVER} = $server or croak "Constructor server failed";
61 1 50       5 warn "server set to '$server'\n" if $self->{DEBUG};
62 1         4 bless($self, $class);
63 1 50       5 unless ($self->encoding("__GUESS__")) {
64 0         0 carp "Constructor encoding() failed";
65 0         0 return undef;
66             }
67 1 50       13 if ($args{'ConnectTimeout'}) {
68 0         0 $self->{TIMEOUT} = $args{'ConnectTimeout'};
69 0 0       0 warn "timeout ".$self->{TIMEOUT}."\n" if $self->{DEBUG};
70             }
71 1 50       5 if ($args{'Port'}) {
72 0         0 $self->{PORT} = $args{'Port'};
73 0 0       0 warn "port set to ".$self->{PORT}."\n" if $self->{DEBUG};
74             }
75 1 50       4 unless ($args{'DeferConnect'}) {
76 0 0       0 warn "calling reconnect()\n" if $self->{DEBUG};
77 0 0       0 $self->reconnect() or return undef;
78             }
79 1 50       4 warn "constructor finished\n" if $self->{DEBUG};
80 1         4 return $self;
81             }
82              
83             sub reconnect {
84 0     0 1 0 my $self = shift;
85 0 0       0 ref($self) or croak;
86              
87 0         0 my $sock = $self->{SOCKET};
88              
89             # if have a socket, disconnect first
90 0 0       0 if (defined($sock)) {
91             #carp "Socket is already defined";
92             #return undef;
93 0 0       0 $self->disconnect() or return undef;
94             }
95              
96             # Applying timeout() to socket seems to fail. Hmm.
97 0 0       0 warn "opening socket to " . $self->{SERVER} . "\n" if $self->{DEBUG};
98 0 0       0 if ($self->{TIMEOUT}) {
99 0 0       0 warn "socket timeout ".$self->{TIMEOUT}."\n" if $self->{DEBUG};
100 0 0       0 $sock = IO::Socket::INET->new(
101             PeerAddr => $self->{SERVER},
102             PeerPort => $self->{PORT},
103             Timeout => $self->{TIMEOUT},
104             Proto => 'tcp') or return undef;
105             } else {
106 0 0       0 warn "socket default timeout\n" if $self->{DEBUG};
107 0 0       0 $sock = IO::Socket::INET->new(
108             PeerAddr => $self->{SERVER},
109             PeerPort => $self->{PORT},
110             Proto => 'tcp') or return undef;
111             }
112              
113 0         0 binmode($sock);
114 0         0 $self->{SOCKET} = $sock;
115 0         0 $sock->autoflush();
116 0 0       0 warn "socket opened to " . $sock->peerhost() . "\n" if $self->{DEBUG};
117 0         0 $self->{CONNECTTIME} = time;
118 0 0       0 warn "connected at " . $self->{CONNECTTIME} . "\n" if $self->{DEBUG};
119 0         0 return $self->{SOCKET};
120             }
121              
122             sub disconnect {
123 1     1 1 150 my $self = shift;
124 1 50       4 ref($self) or croak;
125              
126 1         3 my $sock = $self->{SOCKET};
127              
128             # can't disconnect if no socket
129 1 50       5 if (!defined($sock)) {
130 1         210 carp "Socket is not defined";
131 1         163 return undef;
132             }
133              
134             ##
135             ## Only on newer perls
136             ##
137             ### can't disconnect if not connected
138             ##if (!$sock->connected()) {
139             ## carp "Socket is not connected";
140             ## $self->{SOCKET} = undef;
141             ## return undef;
142             ##}
143              
144 0 0       0 warn "closing socket to " . $sock->peerhost() . "\n" if $self->{DEBUG};
145 0 0       0 unless (close $sock) {
146 0         0 carp "Cannot close socket: $!";
147 0         0 return undef;
148             }
149 0         0 $self->{SOCKET} = undef;
150 0 0       0 warn "socket closed (was open for " . (time - $self->{CONNECTTIME}) .
151             "s)\n" if $self->{DEBUG};
152 0         0 return 1;
153             }
154              
155             sub encoding {
156 1     1 1 3 my $self = shift;
157 1 50       5 ref($self) or croak;
158 1 50       4 my $e = shift or return $self->{ENCODING};
159              
160             # guess from input record seperator
161 1 50       5 if ($e eq "__GUESS__") {
    0          
    0          
162 1 50       13 warn "guessing encoding\n" if $self->{DEBUG};
163 1 50       5 if ($/ eq "\015\012") { # CRLF: Dos/Win
164 0         0 $self->{ENCODING} = "\015";
165 0 0       0 warn "guessed carraige-return encoding\n" if $self->{DEBUG};
166             } else { # LF: Unix-like
167 1         4 $self->{ENCODING} = "\012";
168 1 50       3 warn "guessed line-feed encoding\n" if $self->{DEBUG};
169             }
170              
171             # specific encoding requested
172             } elsif ($e eq "dos") {
173 0         0 $self->{ENCODING} = "\015";
174 0 0       0 warn "set carraige-return encoding\n" if $self->{DEBUG};
175             } elsif ($e eq "unix") {
176 0         0 $self->{ENCODING} = "\012";
177 0 0       0 warn "set line-feed encoding\n" if $self->{DEBUG};
178             } else {
179 0         0 croak "Unknown encoding: '$e'";
180 0         0 $self->{ENCODING} = undef;
181             }
182              
183 1         5 return $self->{ENCODING};
184             }
185              
186             sub server {
187 1     1 1 160 my $self = shift;
188 1 50       4 ref($self) or croak;
189 1 50       5 $self->{SERVER} = shift if @_;
190 1 50       3 warn "server is " . $self->{SERVER} . "\n" if $self->{DEBUG};
191 1         4 return $self->{SERVER};
192             }
193              
194             sub sender {
195 3     3 1 162 my $self = shift;
196 3 50       8 ref($self) or croak;
197 3 100       8 $self->{SENDER} = shift if @_;
198 3 50       7 warn "sender is " . $self->{SENDER} . "\n" if $self->{DEBUG};
199 3         10 return $self->{SENDER};
200             }
201              
202             sub recipient {
203 4     4 1 164 my $self = shift;
204 4 50       11 ref($self) or croak;
205 4 100       10 push(@{$self->{RECIPIENTS}}, shift) if @_;
  2         5  
206 4 50       10 warn "recipients are ". join(",", @{$self->{RECIPIENTS}}) .
  0         0  
207             "\n" if $self->{DEBUG};
208 4         18 return $self->{RECIPIENTS};
209             }
210              
211             sub message {
212 0     0 1 0 my $self = shift;
213 0 0       0 ref($self) or croak;
214 0 0       0 warn "message() started\n" if $self->{DEBUG};
215 0 0       0 if ($self->{MSGFILE}) {
216 0         0 carp "Message already created by message_from_file()";
217 0         0 return undef;
218             }
219 0 0       0 $self->{MESSAGE} .= shift if @_;
220 0 0       0 warn "message text appended (is now " . length($self->{MESSAGE}) .
221             " bytes)\n" if $self->{DEBUG};
222 0         0 return $self->{MESSAGE};
223             }
224              
225             sub message_from_file {
226 0     0 1 0 my $self = shift;
227 0 0       0 ref($self) or croak;
228 0 0       0 warn "message_from_file() started\n" if $self->{DEBUG};
229 0 0       0 if (defined($self->{MESSAGE})) {
230 0         0 carp "Message already created by message()";
231 0         0 return undef;
232             }
233 0 0       0 my $f = shift or return $self->{MSGFILE};
234             #
235             # This is permitted in case the file needs to be created/modified
236             # by some subsequent process
237             ## -f $f or return undef;
238             #
239 0 0       0 warn "message_from_file file is '$f'\n" if $self->{DEBUG};
240 0         0 $self->{MSGFILE} = $f;
241 0         0 return $self->{MSGFILE};
242             }
243              
244             sub new_message {
245 0     0 1 0 my $self = shift;
246 0 0       0 ref($self) or croak;
247              
248 0         0 $self->{MESSAGE} = undef;
249 0         0 $self->{MSGFILE} = undef;
250 0 0       0 warn "message reset\n" if $self->{DEBUG};
251 0         0 return 1;
252             }
253              
254             sub new_envelope {
255 1     1 1 162 my $self = shift;
256 1 50       20 ref($self) or croak;
257              
258 1         3 $self->{SENDER} = undef;
259 1         3 $self->{RECIPIENTS} = [];
260 1 50       4 warn "envelope reset\n" if $self->{DEBUG};
261 1         22 return 1;
262             }
263              
264             sub _send_file {
265 0     0   0 my $self = shift;
266 0 0       0 ref($self) or die;
267 0         0 my $f = $self->{MSGFILE};
268 0         0 my $sock = $self->{SOCKET};
269              
270 0 0       0 warn "_send_file starting\n" if $self->{DEBUG};
271 0 0       0 unless (open(FILE, $f)) {
272 0         0 carp "Cannot open file '$f': $!";
273 0         0 return undef;
274             }
275 0         0 my $size = (stat(FILE))[7];
276 0         0 binmode(FILE);
277             #carp "File '$f' is empty" if $size == 0;
278 0 0       0 if ($size < 0) {
279 0         0 carp "File '$f' has negative size";
280 0         0 return undef;
281             }
282              
283 0         0 my $len;
284 0 0       0 print $sock ($size+1) . ":" . $self->{ENCODING} or return undef;
285 0 0       0 while () { print $sock $_ or return undef; $len += length($_) };
  0         0  
  0         0  
286              
287 0 0       0 if ($size != $len) {
288 0         0 warn "File '$f' should be $size but we read $len\n";
289 0         0 return undef;
290             }
291 0 0       0 print $sock "," or return undef;
292 0 0       0 unless (close FILE) {
293 0         0 carp "Cannot close file '$f': $!";
294 0         0 return undef;
295             }
296 0 0       0 warn "_send_file finished\n" if $self->{DEBUG};
297 0         0 return 1;
298             }
299              
300             sub send {
301 0     0 1 0 my $self = shift;
302 0 0       0 ref($self) or croak;
303              
304 0 0       0 warn "send() running sanity checks\n" if $self->{DEBUG};
305 0 0       0 $self->_ready_to_send() or return undef;
306             ##$self->_session_notexpired() or return undef;
307 0         0 my $sock = $self->{SOCKET};
308              
309 0 0       0 if ($self->{MSGFILE}) {
310 0 0       0 warn "calling _send_file for " . $self->{MSGFILE} .
311             "\n" if $self->{DEBUG};
312 0 0       0 $self->_send_file() or return undef;
313             } else {
314 0 0       0 warn "sending message data\n" if $self->{DEBUG};
315 0 0       0 print $sock netstring_encode($self->{ENCODING} .
316             $self->{MESSAGE}) or return undef;
317             }
318              
319 0         0 my($s, %r);
320              
321 0         0 $s = netstring_encode($self->{SENDER});
322 0 0       0 warn "sending envelope sender $s\n" if $self->{DEBUG};
323 0 0       0 print $sock $s or return undef;
324              
325 0         0 $s = netstring_encode(scalar netstring_encode($self->{RECIPIENTS}));
326 0 0       0 warn "sending envelope recipient(s) $s\n" if $self->{DEBUG};
327 0 0       0 print $sock $s or return undef;
328            
329 0         0 $s = undef;
330 0         0 foreach (@{$self->{RECIPIENTS}}) {
  0         0  
331 0 0       0 warn "read response\n" if $self->{DEBUG};
332 0         0 $s = $self->_read_netstring();
333 0 0       0 warn "parse response: $s\n" if $self->{DEBUG};
334 0 0       0 CASE: {
335 0         0 $s =~ s/^K/success: / and last CASE;
336 0 0       0 $s =~ s/^Z/deferral: / and last CASE;
337 0 0       0 $s =~ s/^D/failure: / and last CASE;
338 0         0 _badproto();
339             }
340 0         0 $r{$_} = $s;
341             }
342              
343 0 0       0 warn "finished send()\n" if $self->{DEBUG};
344 0         0 return \%r;
345             }
346              
347             sub _ready_to_send {
348 0     0   0 my $self = shift;
349 0 0       0 ref($self) or die;
350              
351 0 0       0 warn "_ready_to_send() starting\n" if $self->{DEBUG};
352             # need defined sender (don't need true; empty string valid),
353             # recipient(s), defined message, socket and an encoding
354 0   0     0 return (defined($self->{SENDER}) and scalar(@{$self->{RECIPIENTS}}) and
355             (defined($self->{MESSAGE}) or $self->{MSGFILE}) and
356             $self->{SOCKET} and $self->{ENCODING});
357             }
358              
359             sub _session_notexpired {
360 0     0   0 my $self = shift;
361 0 0       0 ref($self) or die;
362              
363 0 0       0 if (time - $self->{CONNECTTIME} > $self->{SESSIONLIMIT}) {
364 0         0 carp "Session has expired";
365 0         0 $self->disconnect(); # what about failure?
366 0         0 return undef;
367             }
368 0         0 return 1;
369             }
370              
371             sub _read_netstring {
372 0     0   0 my $self = shift;
373 0 0       0 ref($self) or die;
374 0         0 my $sock = $self->{SOCKET};
375              
376 0         0 my $s = netstring_read($sock);
377              
378 0 0 0     0 if (defined $s and netstring_verify($s)) {
379 0         0 return netstring_decode($s);
380             }
381 0         0 return "";
382             }
383              
384             sub _badproto {
385 0     0   0 confess "Protocol violation\n";
386             }
387              
388             sub _badresources {
389 0     0   0 confess "Excessive resources requested\n";
390             }
391              
392             sub DESTROY {
393 1     1   156 my $self = shift;
394 1 50       4 ref($self) or die;
395 1 50       152 $self->disconnect() if $self->{SOCKET}; # don't care about failure
396             }
397              
398             1;
399              
400             __END__