File Coverage

blib/lib/Net/EPP/Client.pm
Criterion Covered Total %
statement 27 86 31.4
branch 0 34 0.0
condition 0 29 0.0
subroutine 9 21 42.8
pod 0 10 0.0
total 36 180 20.0


line stmt bran cond sub pod time code
1             package Net::EPP::Client;
2 1     1   8 use Carp;
  1         8  
  1         140  
3 1     1   717 use IO::Socket::IP;
  1         50837  
  1         6  
4 1     1   1865 use IO::Socket::SSL;
  1         102402  
  1         12  
5 1     1   951 use Net::EPP::Parser;
  1         5  
  1         13  
6 1     1   884 use Net::EPP::Frame::Response;
  1         5  
  1         49  
7 1     1   636 use Net::EPP::Protocol;
  1         4  
  1         45  
8 1     1   7 use bytes;
  1         2  
  1         6  
9 1     1   30 use strict;
  1         3  
  1         24  
10 1     1   5 use warnings;
  1         3  
  1         1582  
11              
12             =pod
13              
14             =head1 NAME
15              
16             Net::EPP::Client - a client library for the
17             L of the L
18             Provisioning Protocol (EPP)|https://www.rfc-editor.org/info/std69>.
19              
20             =head1 SYNOPSIS
21              
22             #!/usr/bin/perl
23             use Net::EPP::Client;
24             use strict;
25              
26             my $epp = Net::EPP::Client->new('host' => 'epp.nic.tld');
27              
28             my $greeting = $epp->connect;
29              
30             $epp->send_frame('login.xml');
31              
32             my $answer = $epp->get_frame;
33              
34             my $answer = $epp->request('');
35              
36             =head1 DESCRIPTION
37              
38             L defines a TCP- (and
39             TLS-) based transport model for EPP, and this module implements a client for
40             that model. You can establish and manage EPP connections and send and receive
41             responses over this connection.
42              
43             C is a low-level EPP client. If you are writing applications,
44             you should use L instead.
45              
46             =head1 CONSTRUCTOR
47              
48             my $epp = Net::EPP::Client->new(%PARAMS);
49              
50             The constructor method creates a new EPP client object. It accepts a number of
51             parameters:
52              
53             =over
54              
55             =item * C
56              
57             MANDATORY. Specifies the computer to connect to. This may be a DNS hostname or
58             an IP address. If a hostname is provided, IPv6 will be used if available.
59              
60             =item * C
61              
62             OPTIONAL. Specifies the TCP port to connect to. This defaults to C<700>.
63              
64             =item * C
65              
66             OPTIONAL. If the value of this parameter is false, then a plaintext
67             connection will be created. Otherwise, L will be used to
68             provide an encrypted connection.
69              
70             =item * C
71              
72             DEPRECATED. If the value of this parameter is false, then the C and
73             C methods (see below) will return strings instead of
74             C objects.
75              
76             =back
77              
78             =cut
79              
80             sub new {
81 0     0 0   my ($package, %params) = @_;
82              
83 0           my $self;
84              
85             #
86             # this is an undocumented and unsupported feature that allows clients to
87             # connect to a local Unix socket instead of a TCP service. IIRC the only
88             # use case for this was the old Net::EPP::Proxy module which went away 𝑛
89             # decades ago, and it will be removed in a future release.
90             #
91 0 0         if (defined($params{'sock'})) {
92             $self = {
93 0           'sock' => $params{'sock'},
94             'ssl' => 0,
95             };
96              
97             } else {
98 0 0         croak("missing hostname") if (!defined($params{'host'}));
99              
100             $self = {
101             'host' => $params{'host'},
102             'port' => $params{'port'} || 700,
103              
104             #
105             # since v0.27, TLS is enabled by default and must be explicitly
106             # disabled.
107             #
108 0 0 0       'ssl' => (exists($params{'ssl'}) && !$params{'ssl'} ? 0 : 1),
      0        
109             };
110             }
111              
112             #
113             # this option will also be removed in a future release.
114             #
115 0 0 0       $self->{'frames'} = (exists($params{'frames'}) && !$params{'frames'} ? 0 : 1);
116              
117 0           return bless($self, $package);
118             }
119              
120             =pod
121              
122             =head1 METHODS
123              
124             =head2 CONNECTING TO A SERVER
125              
126             my $greeting = $epp->connect(%PARAMS);
127              
128             This method establishes the TCP connection. You can use the C<%PARAMS> hash to
129             specify arguments that will be passed on to the constructors for
130             L (such as a timeout) or L (such as
131             certificate information). Which of these modules will be used is determined by
132             the C parameter that was provided when instantiating the object. See the
133             relevant manpage for examples.
134              
135             This method will C if connection fails, so be sure to use C if
136             you want to catch the error.
137              
138             By default, the return value for C will be the EPP EgreetingE
139             frame returned by the server. Please note that the same caveat about blocking
140             applies to this method as to C (see below).
141              
142             If you want to get the greeting yourself, set C<$params{no_greeting}> to C<1>.
143              
144             If TLS is enabled, then you can use C<%params> to configure a client certificate
145             and/or server certificate validation behaviour.
146              
147             =cut
148              
149             sub connect {
150 0     0 0   my ($self, %params) = @_;
151              
152 0 0         croak('already connected') if ($self->connected);
153              
154 0 0         if (defined($self->{'sock'})) {
155 0           $self->_connect_unix(%params);
156              
157             } else {
158 0           $self->_connect_tcp(%params);
159              
160             }
161              
162 0 0         return ($params{'no_greeting'} ? 1 : $self->get_frame);
163             }
164              
165             sub _connect_tcp {
166 0     0     my ($self, %params) = @_;
167              
168 0 0         my $class = ($self->{'ssl'} == 1 ? 'IO::Socket::SSL' : 'IO::Socket::IP');
169              
170             $self->{'connection'} = $class->new(
171             'PeerAddr' => $self->{'host'},
172 0           'PeerPort' => $self->{'port'},
173             'Proto' => 'tcp',
174             'Type' => SOCK_STREAM,
175             %params
176             );
177              
178 0 0 0       if (!defined($self->{'connection'}) || ($@ && $@ ne '')) {
      0        
179 0           chomp($@);
180 0           $@ =~ s/^$class:? ?//;
181 0           croak("Connection to $self->{'host'}:$self->{'port'} failed: $@");
182             }
183              
184 0           return 1;
185             }
186              
187             sub _connect_unix {
188 0     0     my ($self, %params) = @_;
189              
190             $self->{'connection'} = IO::Socket::UNIX->new(
191 0           'Peer' => $self->{'sock'},
192             'Type' => SOCK_STREAM,
193             %params
194             );
195              
196 0 0 0       if (!defined($self->{'connection'}) || ($@ && $@ ne '')) {
      0        
197 0           croak("Connection to $self->{'host'}:$self->{'port'} failed: $@");
198             }
199              
200 0           return 1;
201             }
202              
203             =pod
204              
205             =head2 COMMUNICATING WITH THE SERVER
206              
207             my $answer = $epp->request($question);
208              
209             This is a simple wrapper around C and C (see below).
210             This method accepts a "question" frame as an argument, sends it to the server,
211             and then returns the next frame the server sends back.
212              
213             =cut
214              
215             sub request {
216 0     0 0   my ($self, $frame) = @_;
217 0 0         return $self->get_frame if ($self->send_frame($frame));
218             }
219              
220             =pod
221              
222             =head2 GETTING A FRAME FROM THE SERVER
223              
224             my $frame = $epp->get_frame;
225              
226             This method returns an EPP response frame from the server. This will be a
227             L object unless the C argument to the
228             constructor was false, in which case it will be a string containing a blob of
229             XML.
230              
231             B: this method will block your program until it receives the
232             full frame from the server. That could be a bad thing for your program, so you
233             might want to consider using the C function to apply a timeout, like
234             so:
235              
236             my $timeout = 10; # ten seconds
237              
238             eval {
239             local $SIG{ALRM} = sub { die "alarm\n" };
240             alarm($timeout);
241             my $frame = $epp->get_frame;
242             alarm(0);
243             };
244              
245             if ($@ ne '') {
246             alarm(0);
247             print "timed out\n";
248             }
249              
250             If the connection to the server closes before the response can be received, or
251             the server returned a mal-formed frame, this method will C.
252              
253             =cut
254              
255             sub get_frame {
256 0     0 0   my $self = shift;
257 0           return $self->parse_response(Net::EPP::Protocol->get_frame($self->connection));
258             }
259              
260             sub parse_response {
261 0     0 0   my ($self, $xml) = @_;
262              
263 0           my $doc;
264 0           eval { $doc = $self->parser->parse_string($xml) };
  0            
265 0 0 0       if (!defined($doc) || $@ ne '') {
266 0           chomp($@);
267 0           croak(sprintf("Frame from server wasn't well formed: %s\n\nThe XML looks like this:\n\n%s\n\n", $@, $xml));
268              
269             } else {
270 0           return bless($doc, 'Net::EPP::Frame::Response');
271              
272             }
273             }
274              
275             =pod
276              
277             =head2 SENDING A FRAME TO THE SERVER
278              
279             $epp->send_frame($frame);
280              
281             This sends a request frame to the server. C<$frame> may be one of:
282              
283             =over
284              
285             =item * a scalar containing XML
286              
287             =item * a scalar containing a filename
288              
289             =item * an L object (or an instance of a subclass)
290              
291             =item * an L object (or an instance of a subclass)
292              
293             =back
294              
295             =cut
296              
297             sub send_frame {
298 0     0 0   my ($self, $frame) = @_;
299              
300 0           my $xml;
301 0 0 0       if ($frame->isa('XML::DOM::Document') || $frame->isa('XML::LibXML::Document')) {
    0 0        
302 0           $xml = $frame->toString;
303              
304             } elsif ($frame !~ /
305 0 0         if (!open(FRAME, $frame)) {
306 0           croak("Couldn't open file '$frame' for reading: $!");
307              
308             } else {
309 0           $xml = join('', );
310 0           close(FRAME);
311              
312             }
313              
314             } else {
315 0           $xml = $frame;
316              
317             }
318              
319 0           return Net::EPP::Protocol->send_frame($self->connection, $xml);
320             }
321              
322             =pod
323              
324             =head2 DISCONNECTING FROM THE SERVER
325              
326             $epp->disconnect;
327              
328             This closes the connection. An EPP server should always close a connection after
329             a ElogoutE frame has been received and acknowledged; this method
330             is provided to allow you to clean up on the client side, or close the
331             connection out of sync with the server.
332              
333             =cut
334              
335             sub disconnect {
336 0     0 0   my $self = shift;
337              
338 0 0         if ($self->connected) {
339 0           $self->connection->close;
340 0           delete($self->{'connection'});
341             }
342              
343 0           return 1;
344             }
345              
346             sub parser {
347 0     0 0   my $self = shift;
348 0 0         $self->{'parser'} = Net::EPP::Parser->new if (!$self->{'parser'});
349 0           return $self->{'parser'};
350             }
351              
352             =pod
353              
354             $connected = $epp->connected;
355              
356             Returns a boolean if C has a connection to the server. Note that this
357             connection might have dropped, use C to test it.
358              
359             =cut
360              
361 0     0 0   sub connected { defined(shift->connection) }
362              
363             =pod
364              
365             $socket = $epp->connection;
366              
367             Returns the underlying socket.
368              
369             =cut
370              
371 0     0 0   sub connection { shift->{'connection'} }
372              
373             1;
374              
375             =pod
376              
377             =head1 COPYRIGHT
378              
379             This module is (c) 2008 - 2023 CentralNic Ltd and 2024 Gavin Brown. This module
380             is free software; you can redistribute it and/or modify it under the same terms
381             as Perl itself.
382              
383             =cut