diff --git a/lib/MyWeb/App.pm b/lib/MyWeb/App.pm index 4f036a3..9ce523b 100644 --- a/lib/MyWeb/App.pm +++ b/lib/MyWeb/App.pm @@ -84,17 +84,17 @@ get '/' => sub { prefix '/domain' => sub { - any ['post', 'get'] => '/updateraw/:domain' => sub { + post '/updateraw/:domain' => sub { what_is_next rt_dom_updateraw get_session( qw/login passwd/ ) , get_param( qw/domain zoneupdated/) , get_request( qw/address referer/ ); }; - any ['post', 'get'] => '/update/:domain' => sub { + post '/update/:domain' => sub { what_is_next rt_dom_update get_session( qw/login passwd/ ) - , get_param( qw/type name value ttl priority domain/ ); + , get_param( qw/domain name type priority rdata ttl/ ); }; get '/details/:domain' => sub { @@ -117,25 +117,26 @@ prefix '/domain' => sub { , get_request( qw/address referer/ ); }; - get '/del/:domain/:name/:type/:host/:ttl' => sub { + get '/del/:domain/:name/:ttl/:type/:rdata' => sub { what_is_next rt_dom_del_entry get_session( qw/login passwd/ ) - , get_param( qw/domain name type host ttl/ ) + , get_param( qw/domain name type ttl rdata/ ) , get_request( qw/address referer/ ); }; - get '/mod/:domain/:name/:type/:host/:ttl' => sub { + post '/mod/:domain' => sub { what_is_next rt_dom_mod_entry get_session( qw/login passwd/ ) - , get_param( qw/type name ttl domain name type host ttl - newpriority newtype newhost newname newttl / ) + , get_param( qw/domain + oldpriority oldtype oldname oldttl oldrdata + newpriority newtype newname newttl newrdata/ ) , get_request( qw/address referer/ ); }; - get '/cli/:login/:pass/:domain/:name/:type/:host/:ttl/:ip' => sub { + get '/cli/:login/:pass/:domain/:name/:type/:rdata/:ttl/:ip' => sub { what_is_next rt_dom_cli_mod_entry get_session( qw/login/ ) - , get_param( qw/passwd domain name type host ttl ip/ ); + , get_param( qw/passwd domain name type rdata ttl ip/ ); }; }; diff --git a/lib/app.pm b/lib/app.pm index 6da87b4..20b4f61 100644 --- a/lib/app.pm +++ b/lib/app.pm @@ -70,7 +70,7 @@ sub is_owning_domain { # DOMAIN -sub _get_zone { +sub get_zone { my ($self, $domain) = @_; # say ""; @@ -93,41 +93,13 @@ sub _get_zone { sub add_domain { my ($self, $login, $domain) = @_; $self->db->add_domain($login, $domain); - $self->_get_zone($domain)->addzone() + $self->get_zone($domain)->addzone() } sub delete_domain { my ($self, $domain) = @_; $self->db->delete_domain($domain); - $self->_get_zone($domain)->del() -} - -sub modify_entry { - my ($self, $domain, $entryToModify, $newEntry) = @_; - my $zone = $self->_get_zone($domain)->modify_entry( - $entryToModify, $newEntry ); - $self->update_domain($zone, $domain) -} - -sub delete_entry { - my ($self, $domain, $entryToDelete) = @_; - my $zone = $self->_get_zone($domain)->delete_entry( $entryToDelete ); - $self->update_domain($zone, $domain) -} - -sub update_domain_raw { - my ($self, $zone, $domain) = @_; - $self->_get_zone($domain)->update_raw($zone) -} - -sub update_domain { - my ($self, $zone, $domain) = @_; - $self->_get_zone($domain)->update($zone) -} - -sub get_domain { - my ($self, $domain) = @_; - $self->_get_zone($domain)->get() + $self->get_zone($domain)->del() } sub get_domains { diff --git a/lib/rt/domain.pm b/lib/rt/domain.pm index c64bbc4..24df4c3 100644 --- a/lib/rt/domain.pm +++ b/lib/rt/domain.pm @@ -52,20 +52,13 @@ sub rt_dom_cli_mod_entry { return $res; } - $app->modify_entry( $$param{domain} - , { - type => $$param{type} - , name => $$param{name} - , host => $$param{host} - , ttl => $$param{ttl} - } - , { - newtype => $$param{type} - , newname => $$param{name} - , newhost => $$param{ip} - , newttl => $$param{ttl} - , newpriority => '' - }); + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->get_zonefile(); + $zf->rr_mod( + "$$param{name} $$param{ttl} $$param{type} $$param{rdata}" + , "$$param{name} $$param{ttl} $$param{type} $$param{ip}" + ); + $zone->update( $zf ); $app->disconnect(); }; @@ -88,12 +81,13 @@ sub rt_dom_mod_entry { my @missingitems; - for(qw/type name ttl domain name type host ttl - newtype newhost newname newttl/) { + for(qw/domain + oldtype oldname oldrdata oldttl + newtype newname newrdata newttl/) { push @missingitems, $_ unless($$param{$_}); } - if($$param{type} eq 'MX' && ! $$param{newpriority}) { + if($$param{oldtype} eq 'MX' && ! $$param{newpriority}) { push @missingitems, "newpriority"; } @@ -102,8 +96,9 @@ sub rt_dom_mod_entry { return $res; } - for(qw/type name ttl domain name type host ttl - newpriority newtype newhost newname newttl/) { + for(qw/domain + oldtype oldname oldrdata oldttl + newtype newname newrdata newttl/) { say "$_ : $$param{$_}" if $$param{$_}; } @@ -124,20 +119,23 @@ sub rt_dom_mod_entry { return $res; } - $app->modify_entry( $$param{domain} - , { - type => $$param{type} - , name => $$param{name} - , host => $$param{host} - , ttl => $$param{ttl} - } - , { - newtype => $$param{newtype} - , newname => $$param{newname} - , newhost => $$param{newhost} - , newttl => $$param{newttl} - , newpriority => $$param{newpriority} - }); + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->get_zonefile(); + my $str_old = + "$$param{oldname} $$param{oldttl} $$param{oldtype} $$param{oldrdata}"; + my $str_new = "$$param{newname} $$param{newttl} $$param{newtype} "; + if($$param{newtype} eq "MX") { + $str_new .= "$$param{newpriority} $$param{newrdata}"; + } + else { + $str_new .= "$$param{newrdata}"; + } + + say "old : $str_old"; + say "new : $str_new"; + $zf->rr_mod( $str_old, $str_new); + $zone->update( $zf ); + $app->disconnect(); }; @@ -168,12 +166,13 @@ sub rt_dom_del_entry { return $res; } - $app->delete_entry( $$param{domain}, { - type => $$param{type}, - name => $$param{name}, - host => $$param{host}, - ttl => $$param{ttl} - }); + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->get_zonefile(); + $zf->rr_del_raw( + "$$param{name} $$param{ttl} $$param{type} $$param{rdata}" + ); + $zone->update( $zf ); + $app->disconnect(); }; @@ -324,7 +323,8 @@ sub rt_dom_details { return $res; } - my $zone = $app->get_domain($$param{domain}); + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->get_zonefile(); $app->disconnect(); @@ -333,7 +333,7 @@ sub rt_dom_details { login => $$session{login} , admin => $$user{admin} , domain => $$param{domain} - , domain_zone => $zone->output() + , domain_zone => $zf->dump() , user_ip => $$request{address} }; @@ -341,17 +341,7 @@ sub rt_dom_details { $$res{params}{expert} = 1; } else { - $$res{params}{a} = $zone->a(); - $$res{params}{aaaa} = $zone->aaaa(); - $$res{params}{cname} = $zone->cname(); - $$res{params}{ptr} = $zone->ptr(); - $$res{params}{mx} = $zone->mx(); - $$res{params}{ns} = $zone->ns(); - - for(qw/a aaaa cname ptr mx ns/) { - my $t = $_; - map { $$_{type} = uc $t } @{$$res{params}{$t}}; - } + $$res{params}{zone} = $zf->rr_array_to_array(); } }; @@ -378,7 +368,7 @@ sub rt_dom_update { my @missingitems; - for(qw/type name value ttl domain/) { + for(qw/name ttl type rdata domain/) { push @missingitems, $_ unless($$param{$_}); } @@ -403,34 +393,29 @@ sub rt_dom_update { return $res; } - my $zone = $app->get_domain( $$param{domain} ); + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->get_zonefile(); - # TODO better naming convention - my $entries; - for( $$param{type} ) { - if($_ eq 'A') { $entries = $zone->a } - elsif( $_ eq 'AAAA') { $entries = $zone->aaaa } - elsif( $_ eq 'CNAME') { $entries = $zone->cname } - elsif( $_ eq 'MX') { $entries = $zone->mx } - elsif( $_ eq 'PTR') { $entries = $zone->ptr } - elsif( $_ eq 'NS') { $entries = $zone->ns } - elsif( $_ eq 'TXT') { $entries = $zone->txt } # TODO verify this + my $name = $$param{name}; + $name .= ".$$param{domain}" unless $name =~ /$$param{domain}$/; + my $str_new = "$name $$param{ttl} $$param{type} "; + + my $rdata = $$param{rdata}; + + if($$param{type} =~ /^(CNAME|MX|NS|PTR)$/ && $rdata !~ /\.$/) { + $rdata .= ".$$param{domain}"; } - my $new_entry = { - name => $$param{name} - , class => "IN" - , host => $$param{value} - , ttl => $$param{ttl} - , ORIGIN => $zone->origin - }; + if($$param{type} eq "MX") { + $str_new .= "$$param{priority} $$param{rdata}"; + } + else { + $str_new .= "$$param{rdata}"; + } + $zf->rr_add_raw($str_new); + $zf->new_serial(); + $zone->update( $zf ); - $$new_entry{priority} = $$param{priority} if $$param{type} eq 'MX'; - push @$entries, $new_entry; - - $zone->new_serial(); - - $app->update_domain( $zone , $$param{domain} ); $app->disconnect(); }; @@ -478,13 +463,9 @@ sub rt_dom_updateraw { return $res; } else { - my $success = - $app->update_domain_raw($$param{zoneupdated}, $$param{domain}); - - unless($success) { - $$res{deferred}{errmsg} = q{Problème de mise à jour du domaine.}; - } - + my $zone = $app->get_zone( $$param{domain} ); + my $zf = $zone->update_raw( $$param{zoneupdated} ); + $zone->update( $zf ); $$res{route} = '/domain/details/' . $$param{domain}; } diff --git a/lib/zone.pm b/lib/zone.pm index 8bd0fca..c3bb352 100644 --- a/lib/zone.pm +++ b/lib/zone.pm @@ -10,7 +10,7 @@ use getiface ':all'; use copycat ':all'; use fileutil ':all'; use configuration ':all'; -use Data::Dump qw( dump ); +#use Data::Dump qw( dump ); use zonefile; @@ -86,103 +86,17 @@ sub get_remote_zf_ { "$$self{dnsi}{mycfg}{zonedir}/$$self{domain}" } -sub are_same_records_ { - my ($a, $b) = @_; - - #debug({ a => $a }); - #debug({ b => $b }); - - #$a->{priority} eq $b->{priority} && - ( $$a{name} eq $$b{name} && - $$a{host} eq $$b{host} && - $$a{ttl} == $$b{ttl} ) -} - -# returns the lists of domains of a certain type -sub get_records_ { - my ($zone, $entry) = @_; - - for( lc $$entry{type} ) { - if ($_ eq 'a') { return $zone->a } - elsif ($_ eq 'aaaa') { return $zone->aaaa } - elsif ($_ eq 'cname') { return $zone->cname } - elsif ($_ eq 'ns') { return $zone->ns } - elsif ($_ eq 'mx') { return $zone->mx } - elsif ($_ eq 'ptr') { return $zone->ptr } - } - - die 'Impossible to get the entry type.' -} - sub reload_secondary_dns_servers { my $self = shift; $_->reload_sec($$self{slavedzones}) for(@{$$self{dnsisec}}) } -sub delete_entry { - my ($self, $entryToDelete) = @_; - - my $zone = $self->get(); - - my $records = get_records_ $zone, $entryToDelete; - - if( defined $records ) { - foreach my $i ( 0 .. scalar @{$records}-1 ) { - if(are_same_records_($records->[$i], $entryToDelete)) { - delete $records->[$i]; - } - } - } - - $zone -} - -sub modify_entry { - my ($self, $entryToModify, $newEntry) = @_; - - my $zone = $self->get(); - - my $records = get_records_ $zone, $entryToModify; - - if( defined $records ) { - - foreach my $i ( 0 .. scalar @{$records}-1 ) { - - if(are_same_records_($records->[$i], $entryToModify)) { - - say "ENTRY TO MODIFY"; - - say $records->[$i]->{name} . ' = ' . $newEntry->{newname}; - say $records->[$i]->{host} . ' = ' . $newEntry->{newhost}; - say $records->[$i]->{ttl} . ' = ' . $newEntry->{newttl}; - #say $records->[$i]->{type} . ' = ' . $newEntry->{newtype}; - - $records->[$i]->{name} = $newEntry->{newname}; - $records->[$i]->{host} = $newEntry->{newhost}; - $records->[$i]->{ttl} = $newEntry->{newttl}; - #$records->[$i]->{type} = $newEntry->{newtype}; - - if( $$newEntry{newtype} eq 'MX' ) { - say - $records->[$i]->{priority}.' = '.$newEntry->{newpriority}; - $records->[$i]->{priority} = $newEntry->{newpriority}; - } - } - } - } - - dump($records); - - $zone -} - -sub get { +sub get_zonefile { my $self = shift; my $file = $self->get_remote_zf_(); my $dest = $self->get_ztmp_file_(); copycat ($file, $dest); - zonefile->new(domain => $$self{domain}, zonefile => $dest); } @@ -212,7 +126,7 @@ sub addzone { $zonefile->new_serial(); # update the serial number # write the new zone tmpfile to disk - write_file $f->path, $zonefile->output(); + write_file $f->path, $zonefile->dump(); my $file = $self->get_remote_zf_(); copycat ($tmpfile, $file); # put the final zone on the server @@ -239,7 +153,7 @@ sub update { my $tmpfile = $self->get_ztmp_file_(); # write the new zone tmpfile to disk - write_file $tmpfile, $zonefile->output(); + write_file $tmpfile, $zonefile->dump(); my $file = $self->get_remote_zf_(); copycat ($tmpfile, $file); # put the final zone on the server @@ -258,11 +172,10 @@ sub update_raw { my $zonefile; my $file = $self->get_ztmp_file_(); - # write the updated zone file to disk + # write the updated zone file to disk write_file $file, $zonetext; - eval { $zonefile = zonefile->new(zonefile => $file - , domain => $$self{domain}); }; + eval { $zonefile = zonefile->new(zonefile => $file); }; if( $@ ) { unlink($file); @@ -271,7 +184,7 @@ sub update_raw { unlink($file); - $self->update($zonefile) + $zonefile } sub del { diff --git a/lib/zonefile.pm b/lib/zonefile.pm index 87e553a..ed01e60 100644 --- a/lib/zonefile.pm +++ b/lib/zonefile.pm @@ -1,52 +1,180 @@ package zonefile; use v5.14; +use Net::DNS::RR; +use Net::DNS::ZoneFile; use Moo; -use DNS::ZoneParse; +use utf8; +use URI; +use Data::Dumper; has zone => qw/is rw/ ; -has [ qw/domain/ ] => qw/ is ro required 1/; has [ qw/zonefile/ ] => qw/ is rw required 1/; +# Simple functions to manipulate lists of Net::DNS::RR + +sub rr_array_del { + my ($zones, $rr) = @_; + my @z = grep { $_->plain ne $rr->plain } @$zones; + [ @z ] +} + +sub rr_array_add { + my ($zone, $rr) = @_; + my @already_present = grep { $_->plain eq $rr->plain } @$zone; + push @$zone, $rr unless @already_present; + $zone +} + +sub rr_array_new_serial { + my $zones = shift; + + for(@{$zones}) { + if($_->type =~ /SOA/) { + my $serial = $_->serial; + $_->serial($serial + 1); + } + } + + $zones +} + +sub rr_array_serial { + my $zones = shift; + + for(@{$zones}) { + if($_->type =~ /SOA/) { + return $_->serial; + } + } + + die "Impossible to get the zone serial." +} + +sub rr_array_dump { + my $zone = shift; + my $dump = ''; + + # write the SOA record first + for(@{$zone}) { + if($_->type =~ /SOA/i) { + $dump .= $_->string . "\n"; + } + } + + for(@{$zone}) { + if($_->type !~ /SOA/i) { + $dump .= $_->string . "\n"; + } + } + + $dump +} + + sub BUILD { my ($self) = @_; - my $filename = $$self{zonefile}; - if($filename =~ "://") - { - my $fileuri = URI->new($filename); - $filename = $fileuri->path; - } + my $path = $$self{zonefile}; - $$self{zone} = DNS::ZoneParse->new($filename, $$self{domain}); + # zonefile is the filename + if($$self{zonefile} =~ "://") { + my $fileuri = URI->new($$self{zonefile}); + $path = $fileuri->path; + } + + my $zonefile = Net::DNS::ZoneFile->new( $path ); + my @zone = $zonefile->read; + $$self{zone} = [ @zone ]; } sub new_serial { my $self = shift; - $self->zone->new_serial(); -} - -sub origin { - my $self = shift; - $self->zone->origin(); -} - -sub output { - my $self = shift; - $self->zone->output(); + $$self{zone} = rr_array_new_serial $$self{zone} } sub dump { my $self = shift; - $self->zone->dump(); + rr_array_dump $$self{zone} } -# better encapsulation -sub a { my $self = shift; $self->zone->a } -sub aaaa { my $self = shift; $self->zone->aaaa } -sub cname { my $self = shift; $self->zone->cname } -sub ns { my $self = shift; $self->zone->ns } -sub mx { my $self = shift; $self->zone->mx } -sub ptr { my $self = shift; $self->zone->ptr } -sub txt { my $self = shift; $self->zone->txt } # TODO TEST THIS +sub serial { + my ($self, $rr) = @_; + rr_array_serial $$self{zone} +} + +# remove a raw line that represents the RR +sub rr_del_raw { + my ($self, $rrline) = @_; + my $rr = Net::DNS::RR->new($rrline); + $self->rr_del($rr) +} + +sub rr_del { + my ($self, $rr) = @_; + $$self{zone} = rr_array_del $$self{zone}, $rr +} + +# add a raw line that represents the RR +sub rr_add_raw { + my ($self, $rrline) = @_; + my $rr = Net::DNS::RR->new($rrline); + $self->rr_add($rr) +} + +sub rr_add { + my ($self, $rr) = @_; + $$self{zone} = rr_array_add $$self{zone}, $rr +} + +sub rr_mod { + my ($self, $rrline_old, $rrline_new) = @_; + $self->rr_del_raw($rrline_old); + $self->rr_add_raw($rrline_new); +} + + +sub rr_array_to_array { + my ($self) = shift; + my $rr_list; + + for(@{$$self{zone}}) { + + my @list = split / /, $_->plain; + + my $rr; + $$rr{name} = $list[0]; + $$rr{ttl} = $list[1]; + $$rr{class} = $list[2]; + $$rr{type} = $list[3]; + + if($list[3] =~ /SOA/) { + $$rr{ns} = $list[4]; + $$rr{postmaster} = $list[5]; + $$rr{serial} = $list[6]; + $$rr{refresh} = $list[7]; + $$rr{retry} = $list[8]; + $$rr{expire} = $list[9]; + $$rr{minimum} = $list[10]; + } + elsif($list[3] =~ /^(A(AAA)?|CNAME|NS)$/) { + $$rr{rdata} = $list[4]; + } + elsif($list[3] =~ /^MX$/) { + $$rr{priority} = $list[4]; + $$rr{rdata} = $list[5]; + } + elsif($list[3] =~ /^TXT$/) { + $$rr{rdata} = $_->rdstring; + } + else { + die "This RR is not available : " . $_->plain; + } + + push @$rr_list, $rr; + + } + + $rr_list +} 1; diff --git a/readme.markdown b/readme.markdown index 08bc47f..67e1a5b 100644 --- a/readme.markdown +++ b/readme.markdown @@ -7,7 +7,7 @@ Ce qui permet d'être un remplaçant de DynDNS. ## Outils * [Dancer2](http://perldancer.org/) - * [DNS::ZoneParse](https://metacpan.org/pod/DNS::ZoneParse) + * [Net::DNS](https://metacpan.org/pod/Net::DNS) * [Bootstrap](http://twitter.github.io/bootstrap/) * [DBD::mysql](https://metacpan.org/module/DBD::mysql) * [Moo](https://metacpan.org/pod/Moo) @@ -17,6 +17,6 @@ Ce qui permet d'être un remplaçant de DynDNS. * captcha * demander confirmation avant suppression d'une zone - * rajouter les types de RR manquants dans l'interface (remplacement de - DNS::ZoneParse, ou amélioration) + * rajouter les types de RR manquants dans l'interface (amélioration) * déléguer les zones + * revoir le script de màj automatique d'IP diff --git a/t/008_zonefile.t b/t/008_zonefile.t new file mode 100644 index 0000000..a505ea6 --- /dev/null +++ b/t/008_zonefile.t @@ -0,0 +1,18 @@ +use Test::More; +use Modern::Perl; +use lib 'lib'; +use util ':all'; +use zonefile; + +chdir 'lib'; # TODO hack at 2am + +#map { +# ok +# ( ( is_domain_name $_ ), "is '$_' a domain name" ) +#} qw( foo.bar bar localhost. localhost ); +# +#done_testing; + +my $zf = zonefile->new( zonefile => "../t/zonefile.txt" ); +$zf->new_serial(); +print $zf->dump(); diff --git a/t/zonefile.txt b/t/zonefile.txt new file mode 100644 index 0000000..63c63ec --- /dev/null +++ b/t/zonefile.txt @@ -0,0 +1,17 @@ +$ORIGIN example.com. ; designates the start of this zone file in the namespace +$TTL 1h ; default expiration time of all resource records without their own TTL value +example.com. IN SOA ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h ) +example.com. IN NS ns ; ns.example.com is a nameserver for example.com +example.com. IN NS ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com +example.com. IN MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com +@ IN MX 20 mail2.example.com. ; equivalent to above line, "@" represents zone origin +@ IN MX 50 mail3 ; equivalent to above line, but using a relative host name +example.com. IN A 192.0.2.1 ; IPv4 address for example.com + IN AAAA 2001:db8:10::1 ; IPv6 address for example.com +ns IN A 192.0.2.2 ; IPv4 address for ns.example.com + IN AAAA 2001:db8:10::2 ; IPv6 address for ns.example.com +www IN CNAME example.com. ; www.example.com is an alias for example.com +wwwtest IN CNAME www ; wwwtest.example.com is another alias for www.example.com +mail IN A 192.0.2.3 ; IPv4 address for mail.example.com +mail2 IN A 192.0.2.4 ; IPv4 address for mail2.example.com +mail3 IN A 192.0.2.5 ; IPv4 address for mail3.example.com diff --git a/views/administration.tt b/views/administration.tt index 44135b3..1b9365a 100644 --- a/views/administration.tt +++ b/views/administration.tt @@ -17,7 +17,7 @@ <% FOREACH d IN alldomains %> - <% d.domain %> + <% d.domain %> <% d.login %> diff --git a/views/details.tt b/views/details.tt index 1fadb52..cf08ac2 100644 --- a/views/details.tt +++ b/views/details.tt @@ -17,94 +17,70 @@ - + + - + + - <% FOREACH address in ns %> + <% FOREACH rr in zone %> - - - - - - - - - <% END %> + <% SWITCH rr.type %> + <% CASE [ "A", "AAAA", "TXT", "NS", "CNAME", "PTR" ] %> - <% FOREACH address in a %> - - - - - - - - - - <% END %> + + + + + - <% FOREACH address in aaaa %> - - - - - - - - - - <% END %> + + + + + + - <% FOREACH address in cname %> - - - - - - - - - - - <% END %> - <% FOREACH address in ptr %> - - - - - - - - - - <% END %> + <% CASE "MX" %> - <% FOREACH address in mx %> - - - - - - - - + + + + + + + - + <% CASE %> + + <% END %> + <% END %>
-
NameClass
Domaine TypeHostCible TTL
<% address.class %> - -
<% address.class %> - -
<% address.class %> - -
<% rr.class %>
<% address.class %> + + + + +
<% address.class %> - -
<% address.class %> + + + + + + + + <% rr.class %> + + + + + Resource Record non pris en charge : <% rr.type %>
@@ -134,6 +110,7 @@ + @@ -147,9 +124,9 @@
- +
- +
Votre adresse IP : <% user_ip %>