1package Netdot::Model::RRPTR;
2
3use base 'Netdot::Model';
4use warnings;
5use strict;
6use Scalar::Util qw(blessed);
7
8my $logger = Netdot->log->get_logger('Netdot::Model::DNS');
9
10=head1 Netdot::Model::RRPTR - DNS PTR record Class
11
12=head1 CLASS METHODS
13=cut
14
15##################################################################
16
17=head2 insert
18
19    Override the base method to:
20      - Validate TTL
21      - If not given, figure out the name of the record, using the
22        zone and the IP address
23      - Check for conflicting record types
24
25  Arguments:
26    Any of RRPTRs fields
27  Returns:
28    RRPTR object
29  Examples:
30    $rrptr->insert(ipblock=>$ip);
31
32=cut
33
34sub insert {
35    my($class, $argv) = @_;
36    $class->isa_class_method('insert');
37
38    $class->throw_fatal("Missing required arguments")
39	unless ( $argv->{ptrdname} && $argv->{ipblock} );
40
41    # Convert ipblock into object
42    $argv->{ipblock} = $class->_convert_ipblock($argv->{ipblock});
43    my $ipb = $argv->{ipblock};
44
45    my $rr;
46    if ( !defined $argv->{rr} ){
47	$class->throw_fatal("Figuring out the rr field requires passing zone")
48	    unless ( $argv->{zone} );
49
50	my $zone = blessed($argv->{zone}) ? $argv->{zone} : Zone->retrieve($argv->{zone});
51	unless ( $zone->name =~ /(?:\.in-addr)|(?:\.ip6)\.arpa$/o ){
52	    $class->throw_user(sprintf("Zone %s is not a reverse zone", $zone->name));
53	}
54
55	# This gets us the the FQDN
56	my $name = $class->get_name(ipblock=>$ipb);
57
58	$rr = RR->search(name=>$name)->first;
59	unless ( $rr ){
60	    my $domain = $zone->name;
61	    if ( $domain =~ /^\d+\-\d+\./ ) {
62		# Transform RFC2317 domain name to allowed one
63		$name =~ s/^(\d+).*/$1/;
64	    } else {
65		$name =~ s/\.$domain\.?$//i;
66	    }
67
68	    $rr = RR->insert({zone=>$zone, name=>$name});
69	    $logger->debug("Netdot::Model::RRPTR: Created owner RR for IP: ".
70			   $ipb->get_label." as: ".$rr->get_label);
71	}
72	$argv->{rr} = $rr;
73    }
74
75    $rr = blessed($argv->{rr})? $argv->{rr} : RR->retrieve($argv->{rr});
76    $class->throw_fatal("Invalid rr argument") unless $rr;
77
78    my %linksfrom = RR->meta_data->get_links_from;
79    foreach my $i ( keys %linksfrom ){
80	if ( $rr->$i ){
81	    next if ( $i eq 'ptr_records' || $i eq 'txt_records' || $i eq 'loc_records' );
82	    $class->throw_user($rr->name.": Cannot add PTR records when other conflicting types exist.");
83	}
84    }
85
86    # Sanitize TTL
87    if ( defined $argv->{ttl} && length($argv->{ttl}) ){
88	$argv->{ttl} = $class->ttl_from_text($argv->{ttl});
89    }else{
90	$argv->{ttl} = $rr->zone->default_ttl;
91    }
92
93    $class->_sanitize_ptrdname($argv);
94
95    delete $argv->{zone};
96    return $class->SUPER::insert($argv);
97
98}
99
100##################################################################
101
102=head2 get_name - Figure out record name given IP
103
104  Arguments:
105    Hash containing:
106    ipblock - ipblock object
107  Returns:
108    String
109  Examples:
110    my $name = RRPTR->get_name(ipblock=>$ipb);
111=cut
112
113sub get_name {
114    my ($class, %argv) = @_;
115
116    my $ipblock = $argv{ipblock};
117    unless ( $ipblock ){
118	$class->throw_fatal("RRPTR::get_name: Missing required arguments");
119    }
120
121    my $name;
122    if ( $ipblock->version eq '4' ){
123	$name = join('.', reverse(split(/\./, $ipblock->address)), 'in-addr.arpa');
124    }elsif ( $ipblock->version eq '6' ){
125	$name = $ipblock->full_address;
126	$name =~ s/://g;
127	$name = join('.', reverse(split(//, $name)), 'ip6.arpa');
128    }else {
129	$class->throw_fatal("RRPTR::get_name: Unknown IP version");
130    }
131    return $name;
132}
133
134=head1 INSTANCE METHODS
135=cut
136
137############################################################################
138
139=head2 update
140
141    We override the base method to:
142     - Validate TTL
143
144  Arguments:
145    Hash with field/value pairs
146  Returns:
147    Number of rows updated or -1
148  Example:
149    $record->update(\%args)
150
151=cut
152
153sub update {
154    my($self, $argv) = @_;
155    $self->isa_object_method('update');
156
157    if ( exists $argv->{ipblock} ){
158	unless ( $argv->{ipblock} ){
159	    # Make sure it is not null or 0 if passed
160	    $self->throw_user("IP cannot be null");
161	}
162	# Convert ipblock into object
163	$argv->{ipblock} = $self->_convert_ipblock($argv->{ipblock});
164    }
165
166    if ( defined $argv->{ttl} && length($argv->{ttl}) ){
167	$argv->{ttl} = $self->ttl_from_text($argv->{ttl});
168    }else{
169	# keep what we have
170	delete $argv->{ttl};
171    }
172
173    $self->_sanitize_ptrdname($argv);
174
175    return $self->SUPER::update($argv);
176}
177
178############################################################################
179
180=head2 delete - Delete object
181
182    We override the delete method for extra functionality:
183    - When removing a PTR record, most likely the RR (name)
184    associated with it needs to be deleted too.
185
186  Arguments:
187    None
188  Returns:
189    True if successful.
190  Example:
191    $rrptr->delete;
192
193=cut
194
195sub delete {
196    my ($self, $argv) = @_;
197    $self->isa_object_method('delete');
198
199    my $rr = $self->rr;
200    $self->SUPER::delete();
201
202    # If RR has no more associated records
203    # it should be deleted
204    unless ( $rr->sub_records ){
205	$rr->delete;
206    }
207    return 1;
208}
209
210
211##################################################################
212
213=head2 as_text
214
215    Returns the text representation of this record
216
217  Arguments:
218    None
219  Returns:
220    string
221  Examples:
222    print $rr->as_text();
223
224=cut
225
226sub as_text {
227    my $self = shift;
228    $self->isa_object_method('as_text');
229
230    return $self->_net_dns->string();
231}
232
233
234############################################################################
235# PRIVATE METHODS
236############################################################################
237
238############################################################################
239# _sanitize_ptrdname
240#
241#  Args:
242#    hashref
243#  Returns:
244#    True, or throws exception if validation fails
245#  Examples:
246#    $class->_sanitize_ptrdname($argv);
247#
248sub _sanitize_ptrdname {
249    my ($self, $argv) = @_;
250
251    # Sanitize ptrdname
252    if ( exists $argv->{ptrdname} ){
253	if ( length($argv->{ptrdname}) < 1 || length($argv->{ptrdname}) > 255 ){
254	    $self->throw_user("Invalid domain name size");
255	}
256	$argv->{ptrdname} =~ s/\s+//g;  # remove spaces
257	$argv->{ptrdname} = lc($argv->{ptrdname}); # lowercase
258    }
259    1;
260}
261
262##################################################################
263# check if IP is an address string, if so then convert into object
264sub _convert_ipblock {
265    my ($self, $ip) = @_;
266
267    if ( blessed($ip) ){
268	return $ip;
269    }elsif ( $ip =~ /\D/ ){
270	# value has non-digits
271	if ( my $ipblock = Ipblock->search(address=>$ip)->first ){
272	    return $ipblock;
273	}else {
274	    return Ipblock->insert({address=>$ip, status=>'Static'});
275	}
276    }else{
277	# Value must be an integer, so it might be an id
278	if ( my $ipblock = Ipblock->retrieve($ip) ) {
279	    return $ipblock;
280	}else{
281	    $self->throw_user("Invalid ipblock value: $ip");
282	}
283    }
284}
285
286##################################################################
287sub _net_dns {
288    my $self = shift;
289
290    my $ndo = Net::DNS::RR->new(
291	name     => $self->rr->get_label,
292	ttl      => $self->ttl,
293	class    => 'IN',
294	type     => 'PTR',
295	ptrdname => $self->ptrdname,
296	);
297
298    return $ndo;
299}
300
301
302=head1 AUTHOR
303
304Carlos Vicente, C<< <cvicente at ns.uoregon.edu> >>
305
306=head1 COPYRIGHT & LICENSE
307
308Copyright 2012 University of Oregon, all rights reserved.
309
310This program is free software; you can redistribute it and/or modify
311it under the terms of the GNU General Public License as published by
312the Free Software Foundation; either version 2 of the License, or
313(at your option) any later version.
314
315This program is distributed in the hope that it will be useful, but
316WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
317or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
318License for more details.
319
320You should have received a copy of the GNU General Public License
321along with this program; if not, write to the Free Software Foundation,
322Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
323
324=cut
325
326#Be sure to return 1
3271;
328
329