File Coverage

blib/lib/Net/DRI/Protocol/IRIS/XCP.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             ## Domain Registry Interface, IRIS XCP Connection handling
2             ##
3             ## Copyright (c) 2008-2010 Patrick Mevzek . All rights reserved.
4             ##
5             ## This file is part of Net::DRI
6             ##
7             ## Net::DRI is free software; you can redistribute it and/or modify
8             ## it under the terms of the GNU General Public License as published by
9             ## the Free Software Foundation; either version 2 of the License, or
10             ## (at your option) any later version.
11             ##
12             ## See the LICENSE file that comes with this distribution for more details.
13             ####################################################################################################
14              
15             package Net::DRI::Protocol::IRIS::XCP;
16              
17 1     1   821 use utf8;
  1         4  
  1         6  
18 1     1   29 use strict;
  1         1  
  1         16  
19 1     1   6 use warnings;
  1         2  
  1         19  
20              
21 1     1   183 use XML::LibXML ();
  0            
  0            
22              
23             use Net::DRI::Util;
24             use Net::DRI::Exception;
25             use Net::DRI::Data::Raw;
26             use Net::DRI::Protocol::ResultStatus;
27             use Net::DRI::Protocol::IRIS::Core;
28              
29             =pod
30              
31             =head1 NAME
32              
33             Net::DRI::Protocol::IRIS::XCP - IRIS XCP Connection Handling (RFC4992) for Net::DRI
34              
35             =head1 DESCRIPTION
36              
37             Please see the README file for details.
38              
39             This is only a preliminary basic implementation, with only SASL PLAIN support.
40              
41             There is currently no known public server speaking this protocol.
42              
43             =head1 CURRENT LIMITATIONS
44              
45             =over
46              
47             =item *
48              
49             Nothing is parsed from server greeting message
50              
51             =item *
52              
53             Only SASL PLAIN is handled
54              
55             =item *
56              
57             Blocks split over multiple chunks are not handled, except for application data
58              
59             =item *
60              
61             Nothing is parsed in authentication success result from server
62              
63             =item *
64              
65             Only chunk types "application data", "authentication success" and "authentication failure"
66             are recognized and parsed.
67              
68             =back
69              
70             =head1 SUPPORT
71              
72             For now, support questions should be sent to:
73              
74             Enetdri@dotandco.comE
75              
76             Please also see the SUPPORT file in the distribution.
77              
78             =head1 SEE ALSO
79              
80             Ehttp://www.dotandco.com/services/software/Net-DRI/E
81              
82             =head1 AUTHOR
83              
84             Patrick Mevzek, Enetdri@dotandco.comE
85              
86             =head1 COPYRIGHT
87              
88             Copyright (c) 2008-2010 Patrick Mevzek .
89             All rights reserved.
90              
91             This program is free software; you can redistribute it and/or modify
92             it under the terms of the GNU General Public License as published by
93             the Free Software Foundation; either version 2 of the License, or
94             (at your option) any later version.
95              
96             See the LICENSE file that comes with this distribution for more details.
97              
98             =cut
99              
100             ####################################################################################################
101              
102             sub parse_greeting ## §4.2
103             {
104             my $dr=shift;
105             ## TODO: really parse something ?
106             return Net::DRI::Protocol::ResultStatus->new_success('COMMAND_SUCCESSFUL','Greeting OK','en');
107             }
108              
109             sub read_data # §4
110             {
111             my ($class,$to,$sock)=@_;
112              
113             my $data;
114             $sock->sysread($data,1) or die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','Unable to read registry reply (block header): '.$!,'en'));
115             my $hdr=substr($data,0,1);
116              
117             my $keepopen=parse_block_header($hdr);
118             $to->send_logout() unless ($keepopen); ## will not truly send anything, as there is no logout, but will properly close the socket and prepare everything as needed for next connection
119              
120             ## We do not handle blocks split over multiple chunks, except for application data
121             my $m='';
122             my ($lastchunk,$datacomplete,$chunktype);
123             while(($lastchunk,$datacomplete,$chunktype,$data)=parse_chunk($sock))
124             {
125             if ($chunktype==4+2+1) ## ad=application data
126             {
127             $m.=$data;
128             } elsif ($chunktype==4+0+0)
129             {
130             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Extra SASL data returned by server, currently not handled','en'));
131             } elsif ($chunktype==4+0+1) ## as=authentication success
132             {
133             ## We do not parse anything. If so needed, see §6 of RFC4991, and Core::parse_authentication
134             next;
135             } elsif ($chunktype==4+2+0) ## af=authentication failure
136             {
137             my $doc=XML::LibXML->new()->parse_string(Net::DRI::Util::decode_utf8($data));
138             my $root=$doc->getDocumentElement();
139             my ($msg,$lang,$ri)=Net::DRI::Protocol::IRIS::Core::parse_authentication($root);
140              
141             if (!defined $msg || !defined $lang) { die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Authentication failure without any data','en')); }
142             die(Net::DRI::Protocol::ResultStatus->new_error('AUTHENTICATION_ERROR',$msg,$lang,$ri));
143             } else
144             {
145             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Chunk type not handled: '.$chunktype,'en'));
146             }
147              
148             last if $lastchunk==1;
149             }
150             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED','Last chunk does not have DC=1','en')) unless $datacomplete==1; ## TODO: does that happen IRL ?
151             $m=Net::DRI::Util::decode_utf8($m); ## do it only once at end, when all chunks of application data were joined together again
152              
153             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR',$m? 'Got unexpected reply message: '.$m : '','en')) unless ($m=~m!\s*$!s); ## we do not handle other things than plain responses (see Message)
154             return Net::DRI::Data::Raw->new_from_xmlstring($m);
155             }
156              
157             sub write_message ## §5
158             {
159             my ($self,$to,$msg)=@_;
160              
161             my $hdr='00100000'; ## V=0, KO=1 (Keep Open please)
162             my $auth=Net::DRI::Util::encode_utf8($msg->authority());
163             return pack('B8',$hdr).pack('C',length($auth)).$auth.write_chunk('sasl',$to).write_chunk('data',$msg->as_string());
164             }
165              
166             sub keepalive
167             {
168             my ($class,$cm)=@_;
169             my $mes=$cm->();
170             ## TODO: update IRIS/Message to handle this kind of messages
171             return $mes; ## TODO: update write_message to handle various types (should be infered from content of message probably)
172             }
173              
174             ####################################################################################################
175              
176             sub parse_block_header ## §5
177             {
178             my $d=shift; ## one-octet
179             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','Unable to read 1 byte block header','en')) unless $d;
180             my $hdr=unpack('C',$d);
181             my $ver=($hdr & (128+64)) >> 6;
182             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Version unknown in block header: '.$ver,'en')) unless $ver==0;
183             my $keepopen=($hdr & 32) >> 5;
184             my $res=($hdr & (16+8+4+2+1));
185             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Reserved part unknown in block header: '.$res,'en')) unless $res==0;
186             return $keepopen;
187             }
188              
189             sub parse_chunk_header ## §6
190             {
191             my $d=shift; ## one-octet
192             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','Unable to read 1 byte chunk header','en')) unless $d;
193             my $hdr=unpack('C',$d);
194              
195             my $lc=($hdr & 128) >> 7; ## is last chunk in reply ?
196             my $dc=($hdr & 64) >> 6; ## is data complete with this chunk ?
197             my $res=($hdr & (32+16+8)) >> 3;
198             die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_SYNTAX_ERROR','Reserved part unknown in chunk header: '.$res,'en')) unless $res==0;
199             my $ct=($hdr & (4+2+1)); ## chunk type
200              
201             return ($lc,$dc,$ct);
202             }
203              
204             sub parse_chunk ## §6
205             {
206             my $sock=shift;
207             my $data;
208              
209             $sock->sysread($data,3) or die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','Unable to read registry reply (chunk header of 3 bytes): '.$!,'en'));
210             my $hdr=substr($data,0,1);
211             my @hdr=parse_chunk_header($hdr);
212             my $length=unpack('n',substr($data,1,2));
213             $data=undef;
214             $sock->sysread($data,$length) or die(Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','Unable to read registry reply (chunk data of '.$length.' bytes): '.$!,'en'));
215             return (@hdr,$data);
216             }
217              
218             ## We handle only 'application data' type and sasl plain
219             sub write_chunk
220             {
221             my ($type,$data)=@_;
222             my $hdr;
223             if ($type eq 'data')
224             {
225             $hdr='11000111'; ## LC=yes, DC=yes, CT=ad
226             $data=Net::DRI::Util::encode_utf8($data);
227             } elsif ($type eq 'nodata')
228             {
229             $hdr='11000000';
230             $data='';
231             } elsif ($type eq 'sasl')
232             {
233             my $t=$data->transport_data(); ## $data=$to here
234             unless (exists $t->{client_login} && $t->{client_login} && exists $t->{client_password} && $t->{client_password}) { return ''; }
235             $hdr='01000100'; ## LC=no, DC=yes, CT=sd
236             ## Only SASL PLAIN is supported for now
237             my $sasltype='PLAIN';
238             $data=pack('C',length($sasltype)).$sasltype;
239             my $sasldata=Net::DRI::Util::encode_utf8(sprintf('%s %s %s',$t->{client_login},chr(0),$t->{client_password})); ## authcid=LOGIN, authzid=NULL, password=PASSWORD
240             $data.=pack('n',length($sasldata)).$sasldata;
241             }
242             return pack('B8',$hdr).pack('n',length($data)).$data;
243             }
244              
245             sub transport_default
246             {
247             my ($self,$tname)=@_;
248             return (has_state => 1, type => 'tcp');
249             }
250              
251             ####################################################################################################
252             1;