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