1#!/usr/local/bin/perl -w 2###################################################################### 3# 4# DNS/Zone/Record.pm 5# 6# $Id: Record.pm,v 1.5 2003/02/04 15:37:35 awolf Exp $ 7# $Revision: 1.5 $ 8# $Author: awolf $ 9# $Date: 2003/02/04 15:37:35 $ 10# 11# Copyright (C)2001-2003 Andy Wolf. All rights reserved. 12# 13# This library is free software; you can redistribute it and/or 14# modify it under the same terms as Perl itself. 15# 16###################################################################### 17 18package DNS::Zone::Record; 19 20use strict; 21 22use vars qw($AUTOLOAD); 23 24my $VERSION = '0.85'; 25my $REVISION = sprintf("%d.%02d", q$Revision: 1.5 $ =~ /(\d+)\.(\d+)/); 26 27my %fields = ( 28 OS => undef, 29 CPU => undef, 30 TTL => undef, 31 TYPE => undef, 32 TEXT => undef, 33 CNAME => undef, 34 EMAIL => undef, 35 RETRY => undef, 36 SERIAL => undef, 37 EXPIRE => undef, 38 DOMAIN => undef, 39 COMMENT => undef, 40 ADDRESS => undef, 41 NSERVER => undef, 42 REFRESH => undef, 43 MINIMUM => undef, 44 PROTOCOL => undef, 45 SERVICES => undef, 46 EXCHANGE => undef, 47 PREFERENCE => undef, 48); 49 50### 51# Default type is '' and represents a 52# comment. All other data is optional. 53# When omitted TTL defaults to 0. 54### 55sub new { 56 my($pkg, $ttl, $type, $data) = @_; 57 my $class = ref($pkg) || $pkg; 58 59 my $self = { 60 '_ID' => undef, 61 }; 62 63 $self->{'TYPE'} = $type || ''; 64 $self->{'TTL'} = $ttl || 0; 65 66 if($type eq 'IN A') { 67 $self->{'ADDRESS'} = $data; 68 } 69 elsif($type eq 'IN CNAME') { 70 $self->{'CNAME'} = lc $data; 71 $self->{'CNAME'} =~ s/\.$//; 72 } 73 elsif($type eq 'IN HINFO') { 74 ( 75 $self->{'CPU'}, 76 $self->{'OS'} 77 ) = split /\s+/, $data; 78 } 79 elsif($type eq 'IN MX') { 80 ($self->{'PREFERENCE'}, $self->{'EXCHANGE'}) = split /\s+/, $data; 81 $self->{'PREFERENCE'} = lc $self->{'PREFERENCE'}; 82 $self->{'EXCHANGE'} = lc $self->{'EXCHANGE'}; 83 $self->{'EXCHANGE'} =~ s/\.$//; 84 } 85 elsif($type eq 'IN NS') { 86 $self->{'NSERVER'} = lc $data; 87 $self->{'NSERVER'} =~ s/\.$//; 88 } 89 elsif($type eq 'IN PTR') { 90 $self->{'DOMAIN'} = lc $data; 91 $self->{'DOMAIN'} =~ s/\.$//; 92 } 93 elsif($type eq 'IN SOA') { 94 $data =~ s/\(|\)//g; 95 96 ( 97 $self->{'NSERVER'}, 98 $self->{'EMAIL'}, 99 $self->{'SERIAL'}, 100 $self->{'REFRESH'}, 101 $self->{'RETRY'}, 102 $self->{'EXPIRE'}, 103 $self->{'MINIMUM'} 104 ) = split /\s+/, $data; 105 106 $self->{'NSERVER'} = lc $self->{'NSERVER'}; 107 $self->{'NSERVER'} =~ s/\.$//; 108 109 $self->{'EMAIL'} = lc $self->{'EMAIL'}; 110 $self->{'EMAIL'} =~ s/\.$//; 111 112 $self->{'SERIAL'} = lc $self->{'SERIAL'}; 113 $self->{'REFRESH'} = lc $self->{'REFRESH'}; 114 $self->{'RETRY'} = lc $self->{'RETRY'}; 115 $self->{'EXPIRE'} = lc $self->{'EXPIRE'}; 116 $self->{'MINIMUM'} = lc $self->{'MINIMUM'}; 117 } 118 elsif($type eq 'IN TXT') { 119 $self->{'TEXT'} = $data; 120 } 121 elsif($type eq 'IN WKS') { 122 ( 123 $self->{'ADDRESS'}, 124 $self->{'PROTOCOL'}, 125 $self->{'SERVICES'} 126 ) = split /\s+/, $data; 127 } 128 else { 129 $self->{'COMMENT'} = $data; 130 $self->{'TYPE'} = ''; 131 } 132 133 bless $self, $class; 134 135 return $self; 136} 137 138# The id shall only be used to search if 139# the backend allows to use ids more 140# efficiently. Setting this attribute 141# should only be done when reading/writing 142# from/to the backend (e.g. database) 143######################################## 144sub id { 145 my($self, $id) = @_; 146 147 $self->{'_ID'} = $id if($id); 148 149 return($self->{'_ID'}); 150} 151 152sub data { 153 my($self) = @_; 154 155 my $type = $self->type(); 156 157 if($type eq 'IN SOA') { 158 return( 159 $self->nserver() . ". " . 160 $self->email() . ". " . 161 $self->serial() . " " . 162 $self->refresh() . " " . 163 $self->retry() . " " . 164 $self->expire() . " " . 165 $self->minimum() 166 ); 167 } 168 elsif($type eq 'IN A') { 169 return( 170 $self->address() 171 ); 172 } 173 elsif($type eq 'IN NS') { 174 return( 175 $self->nserver() . "." 176 ); 177 } 178 elsif($type eq 'IN MX') { 179 return( 180 $self->preference() . " " . 181 $self->exchange() . "." 182 ); 183 } 184 elsif($type eq 'IN CNAME') { 185 return( 186 $self->cname() . "." 187 ); 188 } 189 elsif($type eq 'IN HINFO') { 190 return( 191 $self->cpu() . " " . 192 $self->os() 193 ); 194 } 195 elsif($type eq 'IN PTR') { 196 return( 197 $self->domain() 198 ); 199 } 200 elsif($type eq 'IN TXT') { 201 return( 202 $self->text() 203 ); 204 } 205 elsif($type eq 'IN WKS') { 206 return( 207 $self->address() . " " . 208 $self->protocol() . " " . 209 $self->services() 210 ); 211 } 212 else { 213 return( 214 "; " . $self->comment() 215 ); 216 } 217 218 return undef; 219} 220 221sub dump { 222 my($self, $label, $format, $ttl_default) = @_; 223 224 my $ttlstring = ($self->ttl() == $ttl_default) ? '' : $self->ttl(); 225 226 if($self->type() eq 'IN SOA') { 227 printf "$format %s IN SOA %s. %s. \(\n", $label, $ttlstring, $self->nserver(), $self->email(); 228 printf "$format %s ; Serial\n" , '', $self->serial(); 229 printf "$format %s ; Refresh\n", '', $self->refresh(); 230 printf "$format %s ; Retry\n" , '', $self->retry(); 231 printf "$format %s ; Expire\n" , '', $self->expire(); 232 printf "$format %s ; Minimum\n", '', $self->minimum(); 233 printf "$format \)", ''; 234 print " " if($self->comment()); 235 } 236 elsif($self->type() ne '') { 237 my $out_format = "$format %s %-9s %s"; 238 printf $out_format, $label, $ttlstring, $self->type(), $self->data(); 239 print " " if($self->comment()); 240 } 241 242 print "; " . $self->comment() if($self->comment()); 243 print "\n"; 244 245 return $self; 246} 247 248sub toXML { 249 my($self) = @_; 250 my $result; 251 252 $result .= qq(<Record id=") . $self->id() . qq(">\n); 253 $result .= qq(<TTL>) . $self->ttl() . qq(</TTL>\n); 254 $result .= qq(<Type>) . $self->type() . qq(</Type>\n); 255 $result .= qq(<Content>) . $self->data() . qq(</Content>\n); 256 $result .= qq(</Record>\n); 257 258 return $result; 259} 260 261sub debug { 262 my($self) = @_; 263 264 eval { 265 use Data::Dumper; 266 267 print Dumper($self); 268 }; 269 270 return $self; 271} 272 273sub AUTOLOAD { 274 my($self, $value) = @_; 275 my $type = ref($self) or die "$self is not an object"; 276 277 my $name = $AUTOLOAD; 278 $name =~ s/.*://; 279 $name =~ tr/a-z/A-Z/; 280 281 die "Can't access `$name' field in class $type" unless (exists $fields{$name}); 282 283 if(($name eq 'CNAME') || 284 ($name eq 'EMAIL') || 285 ($name eq 'DOMAIN') || 286 ($name eq 'NSERVER') || 287 ($name eq 'EXCHANGE') 288 ) { 289 $value = lc $value; 290 } 291 292 if ($value) { 293 if(($name eq 'TYPE') || ($name eq 'COMMENT')) { 294 die "Read-only attribute `$name' in class $type"; 295 } 296 297 return $self->{$name} = $value; 298 } else { 299 return $self->{$name}; 300 } 301} 302 303sub DESTROY { 304} 305 306sub check { 307 my($self) = @_; 308 309 #unless(isipaddr($self->{address})) {} 310 #unless(isrealhost($self->{cname}) {} 311 #0 <= $self->{preference} <= 65535 312 #unless(isrealhost{$self->{exchange}) {} 313 #unless(isrealhost{$self->{nserver}) {} 314 #unless(isrealhost{$self->{domain}) {} 315 #unless(isrealhost{$self->{nserver}) {} 316 #unless(isemail($self->{email}) {} 317 # 0 <= $self->{serial} <= 4294967295 318 #unless(abs($self->{serial}) == $self->{serial}) {} 319 #unless($self->{serial} > 1995000000) {} 320 # 0 <= $self->{refresh} <= 4294967295 321 # 0 <= $self->{retry} <= 4294967295 322 # 0 <= $self->{expire} <= 4294967295 323 # 0 <= $self->{minimum} <= 4294967295 324 325 return undef; 326} 327 328sub isipaddr { 329 /^(\s+)\.(\s+)\.(\s+)\.(\s+)\.$/; 330} 331 332sub isreverseip { 333 /\.in-addr\.arpa$/i; 334} 335 336sub isrealhost { 337 #test for existance 338 #might use ping and/or dig 339} 340 341sub isemail { 342 /[\w\-]+\@([\w\-]+\.)+[\w\-]+/; 343} 344 345sub is32bit { 346 ($_[0] >= 0) && ($_[0] <= 4294967295); 347} 348 349sub is16bit { 350 ($_[0] >= 0) && ($_[0] <= 65535); 351} 352 3531; 354 355__END__ 356 357=pod 358 359=head1 NAME 360 361Bind::Zone::Record - Record of a Label in a DNS Zone 362 363 364=head1 SYNOPSIS 365 366use DNS::Zone::Record; 367 368my $record = new DNS::Zone::Record($ttl_number, $type_string, $data_string); 369 370$record->dump(); 371$record->debug(); 372 373 374=head1 ABSTRACT 375 376This class represents a record in the domain name service (DNS). 377 378 379=head1 DESCRIPTION 380 381A record has a time-to-live (TTL) value, a type (e.g. 'IN A') 382and some type-secific data (e.g. '123.4.5.6'). You can dump() 383the zone using a standard format and you can use debug() to get 384an output from Data::Dumper that shows the object in detail. 385 386 387=head1 AUTHOR 388 389Copyright (C)2001-2003 Andy Wolf. All rights reserved. 390 391This library is free software; you can redistribute it and/or 392modify it under the same terms as Perl itself. 393 394Please address bug reports and comments to: 395zonemaster@users.sourceforge.net 396 397 398=head1 SEE ALSO 399 400L<DNS::Zone>, L<DNS::Zone::Label>, L<DNS::Zone::File> 401 402 403=cut 404