# by Anarion
# anarion@7a69ezine.org
package DNS::TinyDNS::dnsserver;
our @ISA = qw(DNS::TinyDNS);
our $VERSION = '0.22';
=head1 NAME
DNS::TinyDNS::dnsserver - Perl extension for manipulating dnsserver from djbdns
=head1 SYNOPSIS
use DNS::TinyDNS;
# First create the object
my $dnsserver = DNS::TinyDNS->new(type => 'dnsserver',
dir => '/service/tinydns');
# Get zones
@zones = $dnsserver->list_zones;
# Get one zone
$zone = $dnsserver->get_zone($zones[0]);
# Add entry
$dnsserver->add(zone => $zones[0],
type => 'ns',
host => 'anarion.7a69ezine.org',
ttl => 84500,
);
# Modify ip where tinydns listens
$dnsserver->set_env( IP => '10.0.0.1' );
$dnsserver->restart;
=head1 DESCRIPTION
This module will allow you to manipulate djbdns dnsserver files.
=head1 FUNCTIONS
=over 4
=head2 get_env/set_env
=back
You can set/get the this vars:
IP
ROOT
For further information about every var, consult djbdns server documentation at
C
=over 4
=head2 list_zones
=back
This method returns a list of all the diferent zones configured
my @zones = $dnsserver->list_zones;
=over 4
=head2 get_zone
=back
This method returns a list of hashes with all records of one zone.
The keys of the hash deppends on the type of the record
my @zone_e = $dnsserver->get_zone('catalunya.cat');
The hash have the following keys:
type => String showing the type of the record
('ns','host','mx','alias','reverse')
ttl => ttl of the record
ip => ip of the host
host => host is only set with ns or mx records
priority => is only set with mx records
=over 4
=head2 list
=back
This method return an array of hashes with all records of one type.
Posible types are: mx, ns, host, alias, reverse or all
my @mxs = $dnsserver->list(type => 'mx' ,
zone => '7a69ezine.org');
=over 4
=head2 add
=back
This method adds a mx record
$dnsserver->add(zone => '7a69ezine.org',
type => 'mx',
ip => '10.0.0.1',
host => 'rivendel.7a69ezine.org',
pref => 10,
ttl => 84500,
);
This method adds a ns record
$dnsserver->add(zone => '7a69ezine.org',
type => 'ns',
ip => '10.0.0.1',
host => 'rivendel.7a69ezine.org',
ttl => 84500,
);
This method adds a host record
$dnsserver->add(zone => '7a69ezine.org',
type => 'host',
ip => '10.0.0.1',
host => 'rivendel',
ttl => 84500,
);
This method adds a alias record
$dnsserver->add(zone => '7a69ezine.org',
type => 'alias',
cname=> 'www.7a69ezine.org',
host => 'rivendel',
ttl => 84500,
);
This method add a reverse dns record
$dnsserver->add(zone => '7a69ezine.org',
type => 'reverse',
host => 'anarion',
ip => '10.0.0.13',
);
=over 4
=head2 del
=back
This method delete a mx record
$dnsserver->del(zone => '7a69ezine.org',
type => 'mx',
ip => '10.0.0.1',
host => 'rivendel.7a69ezine.org',
pref => 10,
);
This method delete a ns record
$dnsserver->del(zone => '7a69ezine.org',
type => 'ns',
ip => '10.0.0.1',
host => 'rivendel.7a69ezine.org',
);
This method delete a host record
$dnsserver->del(zone => '7a69ezine.org',
type => 'host',
host => 'rivendel',
ip => '10.0.0.1',
);
This method delete a alias record
$dnsserver->del(zone => '7a69ezine.org',
type => 'alias',
host => 'rivendel',
cname=> 'www.7a69ezine.org',
);
This method delete a reverse dns record
$dnsserver->del(type => 'reverse',
ip => '10.0.0.13',
);
=head1 NOTE
If you want to change from named to bind you can use to methods:
=over 4
Allow bind to transfer the zones from localhost:
perl -lne 'system "tcpclient 127.0.0.1 53 axfr-get $1 zona-$1 zona-$1.tmp" if /zone[^"]+"([^"]+)"/' /home/named/etc/named.conf
Use L and DNS::TinyDNS::dnsserver.
=back
=head1 AUTHOR
Anarion: anarion@7a69ezine.org
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 SEE ALSO
L.
L.
=cut
use Carp;
use Fcntl qw(:DEFAULT :flock);
use Cwd;
my %types = ( 'ns' => '[.&]',
'host' => '[=+]',
'alias' => 'C',
'mx' => '@',
'reverse'=> '\^',
'all' => '[C.&=+@^]');
my %parse = ( 'ns' => \&_parse_ns,
'host' => \&_parse_host,
'alias' => \&_parse_alias,
'mx' => \&_parse_mx,
'reverse'=> \&_parse_reverse,
'all' => \&_parse_all );
sub new
{
my ($clase,$dir)=@_;
my $self = { dir => $dir,
t_env => { IP => '' ,
ROOT => '' },
svc => '/usr/local/bin/svc'
};
return bless $self,$clase;
}
sub start
{
my $self = shift;
my $c_dir = getcwd;
chdir($self->{dir} . '/root')
or carp "Error cant chdir to $self->{dir}";
system "/usr/bin/make"
and carp "Error cant make database";
chdir($c_dir);
$self->SUPER::start();
}
sub restart
{
my $self = shift;
my $c_dir = getcwd;
chdir($self->{dir} . '/root')
or carp "Error cant chdir to $self->{dir}";
system "/usr/bin/make"
and carp "Error cant make database";
chdir($c_dir);
$self->SUPER::restart();
}
sub list
{
my ($self,%options) = @_;
my $file = $self->{dir} . "/root/data";
my (@zone);
local *FILE;
unless($self->{dir} and -f $file)
{
carp "ERROR: dnsserver directory not set";
return 0;
}
unless($options{type} and exists $types{$options{type}})
{
carp "ERROR: this type doesnt exists.";
return 0;
}
open(FILE,$file)
or carp "ERROR: Cant read from $file";
flock(FILE,LOCK_EX)
or carp "Cant lock $file";
seek(FILE,0,0)
or carp "ERROR: Cant seek $file";
while(my $entrada=)
{
chomp($entrada);
if($entrada =~ /^$types{ $options{type} }/)
{
next if ! $options{zone} or
$entrada !~ /^.([\w\-]+\.)*\Q$options{zone}\E:/ and
$entrada !~ /^.[\w.]+\.in-addr.arpa:\Q$options{zone}\E:/;
push(@zone,$parse{ $options{type} }->($entrada));
}
}
close FILE
or carp "Error: Cant Close File";
return @zone;
}
sub list_zones
{
my $self = shift;
my $file=$self->{dir} . "/root/data";
my %zones;
local *FILE;
unless($self->{dir} and -f $file)
{
carp "ERROR: dnsserver directory not set";
return 0;
}
open(FILE,$file)
or carp "ERROR: Cant read from $file";
flock(FILE,LOCK_EX)
or carp "Cant lock $file";
seek(FILE,0,0)
or carp "ERROR: Cant seek $file";
while(my $entrada=)
{
$zones{$1}++ if $entrada=~/^.(?:\d{1,3}\.)([-\w.]+\.arpa):/ or
$entrada=~/^.(?:[\w\-]+\.)*([\w\-]+\.\w{2,4}):/
}
close FILE
or carp "Error: Cant Close File";
return keys %zones;
}
sub get_zone
{
my ($self,$zone) = @_;
my $file = $self->{dir} . "/root/data";
my @zone;
local *FILE;
unless($self->{dir} and -f $file)
{
carp "ERROR: dnsserver directory not set";
return 0;
}
open(FILE,$file)
or carp "ERROR: Cant read from $file";
flock(FILE,LOCK_EX)
or carp "Cant lock $file";
seek(FILE,0,0)
or carp "ERROR: Cant seek $file";
while(my $entrada=)
{
chomp($entrada);
if ($entrada=~/^$types{all}(?:[\w\.\-]*\.)*\Q$zone\E:/ or
$entrada=~/^\^(?:\d{1,3}\.){4}in-addr.arpa:(?:[\w\-]+\.)*\Q$zone\E/)
{
push(@zone,_parse_all->($entrada));
}
}
close FILE
or carp "Error: Cant Close File";
return @zone;
}
sub add
{
my ($self,%options) = @_;
my $file = $self->{dir} . "/root/data";
my $string;
local *FILE;
unless($self->{dir} and -f $file)
{
carp "ERROR: dnsserver directory not set";
return 0;
}
unless(exists $types{ $options{type} })
{
carp "ERROR: this type doesnt exists.";
return 0;
}
open(FILE,">>$file")
or carp "ERROR: Cant read from $file";
flock(FILE,LOCK_EX)
or carp "Cant lock $file";
seek(FILE,0,2)
or carp "ERROR: Cant seek $file";
$options{ttl} ||= 86400;
for($options{type})
{
$string =
/ns/ && do { '.' . join":",@options{qw/zone ip host ttl/} } ||
/mx/ && do { '@' . join ":",@options{qw/zone ip host pref ttl/} } ||
/host/ && do { "+$options{host}." . join ":", @options{qw/zone ip ttl/} } ||
/alias/ && do { "C$options{host}." . join ":", @options{qw/zone cname ttl/} } ||
/reverse/&&do { sprintf("^%d.%d.%d.%d.in-addr.arpa:%s.%s:%d",($options{ip} =~
/\d+/g)[3,2,1,0],@options{'host','zone','ttl'}) } or
carp "What type is ($_) ?";
}
return 0 unless $string;
syswrite(FILE,"$string\n");
close(FILE)
or carp "Error: Cant close file";
}
sub del
{
my ($self,%options) = @_;
my $file = $self->{dir} . "/root/data";
local (*FILE,*FILENEW);
unless($self->{dir} and -f $file)
{
carp "ERROR: dnscache directory not set";
return 0;
}
unless(exists $types{ $options{type} } and $options{zone})
{
carp "ERROR: not enougth arguments.";
return 0;
}
open(FILENEW,">$file.new")
or carp "ERROR: Cant write to $file.new";
flock(FILENEW,LOCK_EX)
or carp "Cant lock $file.new";
open(FILE,"<$file")
or carp "ERROR: Cant read from $file.new";
flock(FILE,LOCK_EX)
or carp "Cant lock $file";
seek(FILE,0,0)
or carp "ERROR: Cant seek $file";
seek(FILENEW,0,0)
or carp "ERROR: Cant seek $file.new";
my $entry;
for($options{type})
{
$entry = /host/ && do { "^[=+]\Q$options{host}.$options{zone}\E" .
"\Q:$options{ip}$options{cname}\E" } ||
/alias/ && do { "^C\Q$options{host}.$options{zone}\E" .
"\Q:$options{cname}\E" } ||
/mx/ && do { "^\@\Q$options{zone}:$options{ip}\E" .
":\Q$options{host}:$options{pref}\E" } ||
/ns/ && do { "^[.&]\Q$options{zone}:$options{ip}\E" .
":\Q$options{host}\E:" } ||
/reverse/&&do { sprintf("^\\^%d.%d.%d.%d.in-addr.arpa:",
($options{ip} =~/\d+/g)[3,2,1,0]) } or
warn "Unknown option ($_)";
}
return 0 unless $entry;
$entry = qr/$entry/;
while(my $entrada=)
{
next if $entrada=~/$entry/;
syswrite(FILENEW,$entrada)
or carp "Cant write to file";
}
close(FILENEW)
or carp "ERROR: Cant close $file.new";
close(FILE)
or carp "ERROR: Cant close $file";
unlink($file)
or carp "ERROR: Cant unlink $file";
rename("$file.new",$file)
or carp "ERROR: Cant rename $file.new to $file";
return $trobat;
}
### PRIVATE SUBS
sub _parse_ns
{
my @data=split/:/,substr($_[0],1);
return { zone => $data[0],
ip => $data[1],
type => 'ns',
host => $data[2],
ttl => $data[3] };
}
sub _parse_mx
{
my @data=split/:/,substr($_[0],1);
return { zone => $data[0],
ip => $data[1],
type => 'mx',
host => $data[2],
ttl => $data[4],
pref => $data[3] };
}
sub _parse_host
{
my @data=split/:/,substr($_[0],1);
my ($host,$zone) = split/\./,$data[0],2;
return { zone => $zone,
host => $host,
type => 'host',
ip => $data[1],
ttl => $data[2] };
}
sub _parse_alias
{
my @data=split/:/,substr($_[0],1);
my ($host,$zone) = split/\./,$data[0],2;
return { zone => $zone,
host => $host,
type => 'alias',
cname=> $data[1],
ttl => $data[2] };
}
sub _parse_reverse
{
my @data=split/:/,substr($_[0],1);
my $ip = join ".", ($data[0]=~/\d+/g)[3,2,1,0];
my ($host,$zone) = split/\./,$data[1],2;
return { zone => $zone,
ip => $ip,
type => 'reverse',
host => $host,
ttl => $data[2] };
}
sub _parse_all
{
my $tipus=substr($_[0],0,1);
my %types = ( '.' => \&_parse_ns,
'&' => \&_parse_ns,
'=' => \&_parse_host,
'+' => \&_parse_host,
'C' => \&_parse_alias,
'^' => \&_parse_reverse,
'@' => \&_parse_mx );
return exists $types{$tipus} ? $types{$tipus}->($_[0]) : 0
}
1;