File Coverage

blib/lib/PowerDNS/Backend/MySQL.pm
Criterion Covered Total %
statement 12 286 4.2
branch 0 102 0.0
condition 0 27 0.0
subroutine 4 30 13.3
pod 23 23 100.0
total 39 468 8.3


line stmt bran cond sub pod time code
1             # Provides an interface to manipulate PowerDNS data in the MySQL Backend.
2              
3             package PowerDNS::Backend::MySQL;
4              
5 1     1   27644 use DBI;
  1         21355  
  1         67  
6 1     1   31 use Carp;
  1         3  
  1         69  
7 1     1   7 use strict;
  1         8  
  1         32  
8 1     1   7 use warnings;
  1         1  
  1         3446  
9              
10             =head1 NAME
11              
12             PowerDNS::Backend::MySQL - Provides an interface to manipulate PowerDNS data in the MySQL Backend.
13              
14             =head1 VERSION
15              
16             Version 0.12
17              
18             =cut
19              
20             our $VERSION = '0.12';
21              
22             =head1 SYNOPSIS
23              
24             use PowerDNS::Backend::MySQL;
25              
26             # Setting parameters and their default values.
27             my $params = { db_user => 'root',
28             db_pass => '',
29             db_name => 'pdns',
30             db_port => '3306',
31             db_host => 'localhost',
32             mysql_print_error => 1,
33             mysql_warn => 1,
34             mysql_auto_commit => 1,
35             mysql_auto_reconnect => 1,
36             lock_name => 'powerdns_backend_mysql',
37             lock_timeout => 3,
38             };
39              
40             my $pdns = PowerDNS::Backend::MySQL->new($params);
41              
42             =head1 DESCRIPTION
43              
44             PowerDNS::Backend::MySQL provides a layer of abstraction
45             for manipulating the data stored in the PowerDNS MySQL backend.
46              
47             =head1 METHODS
48              
49             =head2 new(\%params)
50              
51             my $params = { db_user => 'root',
52             db_pass => '',
53             db_name => 'pdns',
54             db_port => '3306',
55             db_host => 'localhost',
56             mysql_print_error => 1,
57             mysql_warn => 1,
58             mysql_auto_commit => 1,
59             mysql_auto_reconnect => 1,
60             lock_name => 'powerdns_backend_mysql',
61             lock_timeout => 3,
62             };
63              
64             my $pdns = PowerDNS::Backend::MySQL->new($params);
65              
66             Creates a PowerDNS::Backend::MySQL object.
67              
68             =over 4
69              
70             =item db_user
71              
72             The DB user to use when connecting to the MySQL Backend.
73              
74             =item db_pass
75              
76             The DB password to use when connecting to the MySQL Backend.
77              
78             =item db_name
79              
80             The DB name to use when connecting to the MySQL Backend.
81              
82             =item db_port
83              
84             The DB port to use when connecting to the MySQL Backend.
85              
86             =item db_host
87              
88             The DB host to use when connecting to the MySQL Backend.
89              
90             =item mysql_print_error
91              
92             Used to set the DBI::PrintError value.
93              
94             =item mysql_warn
95              
96             Used to set the DBI::Warn value.
97              
98             =item mysql_auto_commit
99              
100             Used to set the DBI::AutoCommit value.
101              
102             =item mysql_auto_reconnect
103              
104             Used to set the DBD::mysql::mysql_auto_reconnect value.
105              
106             =item lock_name
107              
108             Critical sections (adds, deletes, updates on records) get MySQL application level locks
109             (GET_LOCK : http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock);
110             this option can be used to override the default lock name used in those calls.
111              
112             =item lock_timeout
113              
114             Critical sections (adds, deletes, updates on records) get MySQL application level locks
115             (GET_LOCK : http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock);
116             this option can be used to override the default lock timeout used in those calls.
117              
118             =back
119              
120             =cut
121              
122             sub new
123             {
124 0     0 1   my $class = shift;
125 0           my $params= shift;
126 0           my $self = {};
127              
128 0   0       bless $self , ref $class || $class;
129            
130 0 0         my $db_user = defined $params->{db_user} ? $params->{db_user} : 'root';
131 0 0         my $db_pass = defined $params->{db_pass} ? $params->{db_pass} : '';
132 0 0         my $db_name = defined $params->{db_name} ? $params->{db_name} : 'pdns';
133 0 0         my $db_port = defined $params->{db_port} ? $params->{db_port} : '3306';
134 0 0         my $db_host = defined $params->{db_host} ? $params->{db_host} : 'localhost';
135              
136 0 0         $self->{'lock_name'} = defined $params->{lock_name} ? $params->{lock_name} : 'powerdns_backend_mysql';
137 0 0         $self->{'lock_timeout'} = defined $params->{lock_timeout} ? $params->{lock_timeout} : 3;
138              
139 0 0         my $mysql_print_error = $params->{mysql_print_error} ? defined $params->{mysql_print_error} : 1;
140 0 0         my $mysql_warn = $params->{mysql_warn} ? defined $params->{mysql_warn} : 1;
141 0 0         my $mysql_auto_commit = $params->{mysql_auto_commit} ? defined $params->{mysql_auto_commit} : 1;
142              
143 0 0         my $mysql_auto_reconnect = $params->{mysql_auto_reconnect} ? defined $params->{mysql_auto_reconnect} : 1;
144              
145 0           my $db_DSN = "DBI:mysql:database=$db_name;host=$db_host;port=$db_port";
146              
147 0           $self->{'dbh'} = DBI->connect($db_DSN, $db_user, $db_pass,
148             {
149             'PrintError' => $mysql_print_error,
150             'Warn' => $mysql_warn,
151             'AutoCommit' => $mysql_auto_commit,
152             });
153 0           $self->{'dbh'}->{'mysql_auto_reconnect'} = $mysql_auto_reconnect;
154            
155 0           $self->{'error_msg'} = undef;
156              
157 0           return $self;
158             }
159              
160             sub DESTROY
161             {
162 0     0     my $self = shift;
163 0 0         if ( defined $self->{'dbh'} )
164             {
165 0 0         delete $self->{'dbh'} or warn "$!\n";
166             }
167             }
168              
169             # Internal Method.
170             # Get a lock on the database to avoid race conditions.
171             # Returns 1 on success and 0 on failure.
172             sub _lock
173             {
174 0     0     my $self = shift;
175 0           my $lock_name = $self->{'lock_name'};
176 0           my $lock_timeout = $self->{'lock_timeout'};
177              
178 0           my $sth = $self->{'dbh'}->prepare("SELECT GET_LOCK('$lock_name',$lock_timeout)");
179 0 0         if ( ! $sth->execute )
180             {
181 0           return 0;
182             }
183              
184 0           my ($rv) = $sth->fetchrow_array;
185 0           return $rv;
186             }
187              
188             # Internal Method.
189             # Release a lock on the database.
190             # Returns 1 on success and 0 on failure.
191             sub _unlock
192             {
193 0     0     my $self = shift;
194 0           my $lock_name = $self->{'lock_name'};
195              
196 0           my $sth = $self->{'dbh'}->prepare("SELECT RELEASE_LOCK('$lock_name')");
197 0 0         if ( ! $sth->execute )
198             {
199 0           return 0;
200             }
201              
202 0           my ($rv) = $sth->fetchrow_array;
203 0           return $rv;
204             }
205              
206             =head2 add_domain(\$domain)
207              
208             Expects a scalar reference domain name to add to the DB.
209             Returns 1 on success and 0 on failure.
210              
211             =cut
212              
213             sub add_domain($)
214             {
215 0     0 1   my $self = shift;
216 0           my $domain = shift;
217            
218 0           my $sth = $self->{'dbh'}->prepare("INSERT INTO domains (name,type) VALUES (?,'NATIVE')");
219 0 0         if ( $sth->execute($$domain) != 1 ) { return 0; }
  0            
220              
221 0           return 1;
222             }
223              
224             =head2 add_master(\$domain)
225              
226             Expects a scalar reference domain name to add to the DB as type master.
227             Returns 1 on success and 0 on failure.
228              
229             =cut
230              
231             sub add_master($)
232             {
233 0     0 1   my $self = shift;
234 0           my $domain = shift;
235              
236 0           my $sth = $self->{'dbh'}->prepare("INSERT INTO domains (name,type) VALUES (?,'MASTER')");
237 0 0         if ( $sth->execute($$domain) != 1 ) { return 0; }
  0            
238              
239 0           return 1;
240             }
241              
242             =head2 add_slave(\$slave_domain , \$master_ip)
243              
244             Expects two scalar references; first the domain to slave, then the IP address to
245             slave from.
246             Returns 1 on success and 0 on failure.
247             Updates the existing record if there is one, otherwise inserts a new record.
248              
249             =cut
250              
251             sub add_slave($$)
252             {
253 0     0 1   my $self = shift;
254 0           my $domain = shift;
255 0           my $master = shift;
256 0           my $sth;
257              
258 0 0         if ( $self->domain_exists($domain) )
259             {
260 0           $sth = $self->{'dbh'}->prepare("UPDATE domains set master = ? , type = 'SLAVE' WHERE name = ?");
261 0 0         if ( $sth->execute($$master,$$domain) != 1 ) { return 0; }
  0            
262             }
263             else
264             {
265 0           $sth = $self->{'dbh'}->prepare("INSERT INTO domains (name,master,type) VALUES(?,?,'SLAVE')");
266 0 0         if ( $sth->execute($$domain,$$master) != 1 ) { return 0; }
  0            
267             }
268              
269 0           return 1;
270             }
271              
272             =head2 delete_domain(\$domain)
273              
274             Expects a scalar reference domain name to delete from the DB.
275             Returns 1 on success and 0 on failure.
276              
277             =cut
278              
279             sub delete_domain($)
280             {
281 0     0 1   my $self = shift;
282 0           my $domain = shift;
283            
284             # Remove domain.
285 0           my $sth = $self->{'dbh'}->prepare("DELETE FROM domains WHERE name = ?");
286 0 0         if ( $sth->execute($$domain) != 1 ) { return 0; }
  0            
287            
288 0           return 1;
289             }
290              
291             =head2 list_domain_names
292              
293             Does not expect anything.
294             Returns a reference to an array which contains all the domain names
295             listed in the PowerDNS backend.
296              
297             =cut
298              
299             sub list_domain_names
300             {
301 0     0 1   my $self = shift;
302 0           my @domains;
303              
304             # Grab the domain names.
305 0           my $sth = $self->{'dbh'}->prepare("SELECT name FROM domains");
306 0           $sth->execute;
307              
308 0           while ( my ($domain) = $sth->fetchrow_array )
309 0           { push @domains , $domain; }
310              
311 0           return \@domains;
312             }
313              
314             =head2 list_domain_names_by_type(\$type)
315              
316             Expects a scalar reference to a string which is the domain 'type' (i.e. NATIVE, SLAVE, MASTER, etc.)
317             Returns a reference to an array which contains all the domain names of that type.
318              
319             =cut
320              
321             sub list_domain_names_by_type($)
322             {
323 0     0 1   my $self = shift;
324 0           my $type = shift;
325 0           my @domains;
326              
327             # Grab the domain names.
328 0           my $sth = $self->{'dbh'}->prepare("SELECT name FROM domains WHERE type = ?");
329 0           $sth->execute($$type);
330              
331 0           while ( my ($domain) = $sth->fetchrow_array )
332 0           { push @domains , $domain; }
333              
334 0           return \@domains;
335             }
336              
337             =head2 list_slave_domain_names(\$master_ip)
338              
339             Expects a scalar reference to an IP address which is the master IP.
340             Returns a reference to an array which contains all the slave domain names
341             with $master as their 'master'.
342              
343             =cut
344              
345             sub list_slave_domain_names($)
346             {
347 0     0 1   my $self = shift;
348 0           my $master = shift;
349 0           my @domains;
350              
351             # Grab the domain names.
352 0           my $sth = $self->{'dbh'}->prepare("SELECT name FROM domains WHERE TYPE = 'SLAVE' AND master = ?");
353 0           $sth->execute($$master);
354              
355 0           while ( my ($domain) = $sth->fetchrow_array )
356 0           { push @domains , $domain; }
357              
358 0           return \@domains;
359             }
360              
361             =head2 domain_exists(\$domain)
362              
363             Expects a scalar reference to a domain name to be found in the "domains" table.
364             Returns 1 if the domain name is found, and 0 if it is not found.
365              
366             =cut
367              
368             sub domain_exists($)
369             {
370 0     0 1   my $self = shift;
371 0           my $domain = shift;
372            
373 0           my $sth = $self->{'dbh'}->prepare("SELECT id FROM domains WHERE name = ?");
374 0 0         $sth->execute($$domain) or return 0;
375            
376 0           my @record = $sth->fetchrow_array;
377            
378 0 0         scalar(@record) ? return 1 : return 0;
379             }
380              
381             =head2 list_records(\$rr , \$domain)
382              
383             Expects two scalar references; the first to a resource record and the second to a domain name.
384             Returns a reference to a two-dimensional array which contains the resource record name, content,
385             TTL, and priority if any.
386              
387             =cut
388              
389             sub list_records($$)
390             {
391 0     0 1   my $self = shift;
392 0           my $rr = shift;
393 0           my $domain = shift;
394 0           my @records;
395            
396 0           my $sth = $self->{'dbh'}->prepare("SELECT name,content,ttl,prio FROM records WHERE type = ? and domain_id = (SELECT id FROM domains WHERE name = ?)");
397 0           $sth->execute($$rr,$$domain);
398            
399 0           while ( my ($name,$content,$ttl,$prio) = $sth->fetchrow_array )
400 0           { push @records , [ ($name,$content,$ttl,$prio) ]; } # push anonymous array on to end.
401            
402 0           return \@records;
403             }
404              
405             =head2 list_all_records(\$domain)
406              
407             Expects a scalar reference to a domain name.
408             Returns a reference to a two-dimensional array which contains the resource record name, type,
409             content, TTL, and priority if any of the supplied domain.
410              
411             =cut
412              
413             sub list_all_records($)
414             {
415 0     0 1   my $self = shift;
416 0           my $domain = shift;
417 0           my @records;
418            
419 0           my $sth = $self->{'dbh'}->prepare("SELECT name,type,content,ttl,prio FROM records WHERE domain_id = (SELECT id FROM domains WHERE name = ?)");
420 0           $sth->execute($$domain);
421            
422 0           while ( my ($name,$type,$content,$ttl,$prio) = $sth->fetchrow_array )
423 0           { push @records , [ ($name,$type,$content,$ttl,$prio) ]; } # push anonymous array on to end.
424            
425 0           return \@records;
426             }
427              
428             =head2 add_record(\$rr , \$domain)
429              
430             Adds a single record to the backend.
431             Expects two scalar references; one to an array that contains the information for the
432             resource record (name, type, content, ttl, prio); name, type and content are required values.
433             The other scalar reference is the zone you want to add the RR to.
434             Returns 1 if the record was successfully added, and 0 if not.
435              
436             =cut
437              
438             sub add_record($$)
439             {
440 0     0 1   my $self = shift;
441 0           my $rr = shift;
442 0           my $domain = shift;
443 0           my ($name , $type , $content , $ttl , $prio) = @$rr;
444            
445             # Default values.
446 0 0 0       if ( ! defined $ttl or $ttl eq '' ) { $ttl = 7200; }
  0            
447 0 0 0       if ( ! defined $prio or $prio eq '' ) { $prio = 0; }
  0            
448              
449             # Get a server lock to avoid race condition.
450 0 0         if ( ! $self->_lock )
451             {
452 0           carp("Could not obtain lock.\n");
453 0           return 0;
454             }
455              
456 0           my $sth = $self->{'dbh'}->prepare("INSERT INTO records (domain_id,name,type,content,ttl,prio) SELECT id,?,?,?,?,? FROM domains WHERE name = ?");
457 0 0         if ( $sth->execute($name,$type,$content,$ttl,$prio,$$domain) <= 0 )
458             {
459 0           $self->_unlock;
460 0           return 0;
461             }
462              
463             # Release server lock.
464 0           $self->_unlock;
465            
466 0           return 1;
467             }
468              
469             =head2 delete_record(\$rr , \$domain)
470              
471             Deletes a single record from the backend.
472             Expects two scalar references; one to an array that contains the information for the
473             resource record (name, type, content); these are all required values.
474             The other scalar reference is the zone you want to delete the RR from.
475             Returns 1 if the record was successfully deleted, and 0 if not.
476              
477             =cut
478              
479             sub delete_record($$)
480             {
481 0     0 1   my $self = shift;
482 0           my $rr = shift;
483 0           my $domain = shift;
484 0           my ($name , $type , $content) = @$rr;
485              
486             # Get a server lock to avoid race condition.
487 0 0         if ( ! $self->_lock )
488             {
489 0           carp("Could not obtain lock.\n");
490 0           return 0;
491             }
492            
493 0           my $sth = $self->{'dbh'}->prepare("DELETE FROM records WHERE name=? and type=? and content=? and domain_id = (SELECT id FROM domains WHERE name = ?) LIMIT 1");
494 0           my $rv = $sth->execute($name,$type,$content,$$domain);
495              
496             # Release server lock.
497 0           $self->_unlock;
498              
499 0 0         ($rv > 0) ? return 1 : return 0;
500             }
501              
502             =head2 update_record(\$rr1 , \$rr2 , \$domain)
503              
504             Updates a single record in the backend.
505              
506             Expects three scalar references:
507              
508             1) A reference to an array that contains the Resource Record to be updated;
509             ($name , $type , $content) - all required.
510              
511             2) A reference to an array that contains the updated values;
512             ($name , $type , $content , $ttl , $prio) - only $name , $type , $content are required.
513             Defaults for $ttl and $prio will be used if none are given.
514              
515             3) The domain to be updated.
516              
517             Returns 1 on a successful update, and 0 when un-successful.
518              
519             =cut
520              
521             sub update_record($$$)
522             {
523 0     0 1   my $self = shift;
524 0           my $rr1 = shift;
525 0           my $rr2 = shift;
526 0           my $domain = shift;
527 0           my ($name1 , $type1 , $content1) = @$rr1;
528 0           my ($name2 , $type2 , $content2 , $ttl , $prio) = @$rr2;
529            
530             # Default values.
531 0 0 0       if ( ! defined $ttl or $ttl eq '' ) { $ttl = 7200; }
  0            
532 0 0 0       if ( ! defined $prio or $prio eq '' ) { $prio = 0; }
  0            
533            
534             # Get a server lock to avoid race condition.
535 0 0         if ( ! $self->_lock )
536             {
537 0           carp("Could not obtain lock.\n");
538 0           return 0;
539             }
540              
541 0           my $sth = $self->{'dbh'}->prepare("UPDATE records SET name=? , type=? , content=? , ttl=? , prio=? WHERE name=? and type=? and content=? and domain_id = (SELECT id FROM domains WHERE name = ?) LIMIT 1");
542 0           my $rv = $sth->execute($name2,$type2,$content2,$ttl,$prio,$name1,$type1,$content1,$$domain);
543              
544             # Release server lock.
545 0           $self->_unlock;
546            
547 0 0         ($rv > 0) ? return 1 : return 0;
548             }
549              
550             =head2 update_records(\$rr1 , \$rr2 , \$domain)
551              
552             Can update multiple records in the backend.
553              
554             Like update_record() but without the requirement that the 'content' be set in the resource record(s) you are trying to update;
555             also not limited to updating just one record, but can update any number of records that match the resource record you are
556             looking for.
557              
558             Expects three scalar references:
559              
560             1) A reference to an array that contains the Resource Record to be updated;
561             ($name , $type) - all required.
562              
563             2) A reference to an array that contains the updated values;
564             ($name , $type , $content , $ttl , $prio) - only $name , $type , $content are required.
565             Defaults for $ttl and $prio will be used if none are given.
566              
567             3) The domain to be updated.
568              
569             Returns 1 on a successful update, and 0 when un-successful.
570              
571             =cut
572              
573             sub update_records($$$)
574             {
575 0     0 1   my $self = shift;
576 0           my $rr1 = shift;
577 0           my $rr2 = shift;
578 0           my $domain = shift;
579 0           my ($name1 , $type1 ) = @$rr1;
580 0           my ($name2 , $type2 , $content2 , $ttl , $prio) = @$rr2;
581            
582             # Default values.
583 0 0 0       if ( ! defined $ttl or $ttl eq '' ) { $ttl = 7200; }
  0            
584 0 0 0       if ( ! defined $prio or $prio eq '' ) { $prio = 0; }
  0            
585            
586             # Get a server lock to avoid race condition.
587 0 0         if ( ! $self->_lock )
588             {
589 0           carp("Could not obtain lock.\n");
590 0           return 0;
591             }
592              
593 0           my $sth = $self->{'dbh'}->prepare("UPDATE records SET name=? , type=? , content=? , ttl=? , prio=? WHERE name=? and type=? and domain_id = (SELECT id FROM domains WHERE name = ?)");
594             # $rv is number of rows affected; it's OK for no rows to be affected; when duplicate data is being updated for example.
595 0           my $rv = $sth->execute($name2,$type2,$content2,$ttl,$prio,$name1,$type1,$$domain);
596            
597             # Release server lock.
598 0           $self->_unlock;
599              
600 0 0         ($rv > 0) ? return 1 : return 0;
601             }
602              
603             =head2 update_or_add_records(\$rr1 , \$rr2 , \$domain)
604              
605             Can update multiple records in the backend; will insert records if they don't already exist.
606              
607             Expects three scalar references:
608              
609             1) A reference to an array that contains the Resource Record to be updated;
610             ($name , $type) - all required.
611              
612             2) A reference to an array that contains the updated values;
613             ($name , $type , $content , $ttl , $prio) - only $name , $type , $content are required.
614             Defaults for $ttl and $prio will be used if none are given.
615              
616             3) The domain to be updated.
617              
618             Returns 1 on a successful update, and 0 when un-successful.
619              
620             =cut
621              
622             sub update_or_add_records($$$)
623             {
624 0     0 1   my $self = shift;
625 0           my $rr1 = shift;
626 0           my $rr2 = shift;
627 0           my $domain = shift;
628 0           my ($name1 , $type1 ) = @$rr1;
629 0           my ($name2 , $type2 , $content2 , $ttl , $prio) = @$rr2;
630            
631             # Default values.
632 0 0 0       if ( ! defined $ttl or $ttl eq '' ) { $ttl = 7200; }
  0            
633 0 0 0       if ( ! defined $prio or $prio eq '' ) { $prio = 0; }
  0            
634              
635             # Get a server lock to avoid race condition.
636 0 0         if ( ! $self->_lock )
637             {
638 0           carp("Could not obtain lock.\n");
639 0           return 0;
640             }
641              
642             # See if record exists in zone.
643 0           my $sth = $self->{'dbh'}->prepare('SELECT COUNT(*) FROM records WHERE name = ? AND type = ? AND domain_id = (SELECT id FROM domains WHERE name = ?)');
644 0 0         unless ( $sth->execute($name1,$type1,$$domain) )
645             {
646 0           $sth->_unlock;
647 0           return 0;
648             }
649            
650 0           my ($count) = $sth->fetchrow_array;
651              
652 0 0         if ( $count == 0 ) # Add new record to zone.
653             {
654 0           my $sth = $self->{'dbh'}->prepare("INSERT INTO records (domain_id,name,type,content,ttl,prio) SELECT id,?,?,?,?,? FROM domains WHERE name = ?");
655 0 0         if ( $sth->execute($name2,$type2,$content2,$ttl,$prio,$$domain) <= 0 )
656             {
657 0           $self->_unlock;
658 0           return 0;
659             }
660             }
661             else # Update existing record in zone.
662             {
663 0           my $sth = $self->{'dbh'}->prepare("UPDATE records SET name=? , type=? , content=? , ttl=? , prio=? WHERE name=? and type=? and domain_id = (SELECT id FROM domains WHERE name = ?)");
664 0 0         if ( $sth->execute($name2,$type2,$content2,$ttl,$prio,$name1,$type1,$$domain) <=0 )
665             {
666 0           $self->_unlock;
667 0           return 0;
668             }
669             }
670              
671             # Release server lock.
672 0           $self->_unlock;
673 0           return 1;
674             }
675              
676             =head2 find_record_by_content(\$content , \$domain)
677              
678             Finds a specific (single) record in the backend.
679             Expects two scalar references; the first is the content we are looking for, and the second is the domain to be checked.
680             Returns a reference to an array that contains the name and type from the found record, if any.
681              
682             =cut
683              
684             sub find_record_by_content($$)
685             {
686 0     0 1   my $self = shift;
687 0           my $content = shift;
688 0           my $domain = shift;
689            
690 0           my $sth = $self->{'dbh'}->prepare("SELECT name,type FROM records WHERE content = ? AND domain_id = (SELECT id FROM domains WHERE name = ?) LIMIT 1");
691 0           $sth->execute($$content,$$domain);
692            
693 0           my @records = $sth->fetchrow_array;
694            
695 0           return \@records;
696             }
697              
698             =head2 find_record_by_name(\$name, \$domain)
699              
700             Finds a specific (single) record in the backend.
701             Expects two scalar references; the first is the name we are looking for, and the second is the domain to be checked.
702             Returns a reference to an array that contains the content and type from the found record, if any.
703              
704             =cut
705              
706             sub find_record_by_name($$)
707             {
708 0     0 1   my $self = shift;
709 0           my $name = shift;
710 0           my $domain = shift;
711            
712 0           my $sth = $self->{'dbh'}->prepare("SELECT content,type FROM records WHERE name = ? AND domain_id = (SELECT id FROM domains WHERE name = ?) LIMIT 1");
713 0           $sth->execute($$name,$$domain);
714            
715 0           my @records = $sth->fetchrow_array;
716            
717 0           return \@records;
718             }
719              
720             =head2 make_domain_native(\$domain)
721              
722             Makes the specified domain a 'NATIVE' domain.
723             Expects one scalar reference which is the domain name to be updated.
724             Returns 1 upon succes and 0 otherwise.
725              
726             =cut
727              
728             sub make_domain_native($)
729             {
730 0     0 1   my $self = shift;
731 0           my $domain = shift;
732              
733 0           my $sth = $self->{'dbh'}->prepare("UPDATE domains set type='NATIVE' , master='' WHERE name=?");
734 0 0         if ( $sth->execute($$domain) != 1 ) { return 0; }
  0            
735              
736 0           return 1;
737             }
738              
739             =head2 make_domain_master(\$domain)
740              
741             Makes the specified domain a 'MASTER' domain.
742             Expects one scalar reference which is the domain name to be updated.
743             Returns 1 upon succes and 0 otherwise.
744              
745             =cut
746              
747             sub make_domain_master($)
748             {
749 0     0 1   my $self = shift;
750 0           my $domain = shift;
751              
752 0           my $sth = $self->{'dbh'}->prepare("UPDATE domains set type='MASTER' , master='' WHERE name=?");
753 0 0         if ( $sth->execute($$domain) != 1 ) { return 0; }
  0            
754              
755 0           return 1;
756             }
757              
758             =head2 get_domain_type(\$domain)
759              
760             Expects one scalar reference which is the domain name to query for.
761             Returns a string containing the PowerDNS 'type' of the domain given or
762             'undef' if the domain does not exist in the backend or an empty string
763             if the domain has no master (i.e. a NATIVE domain).
764              
765             =cut
766              
767             sub get_domain_type($)
768             {
769 0     0 1   my $self = shift;
770 0           my $domain = shift;
771 0           my $type = '';
772              
773 0           my $sth = $self->{'dbh'}->prepare("SELECT type FROM domains WHERE name = ?");
774 0           $sth->execute($$domain);
775              
776 0           ($type) = $sth->fetchrow_array;
777 0           return $type;
778             }
779              
780             =head2 get_master(\$domain)
781              
782             Expects one scalar reference which is the domain name to query for.
783             Returns a string containing the PowerDNS 'master' of the domain given or
784             'undef' if the domain does not exist in the PowerDNS backend or
785             an empty string if the domain has no master (i.e. a NATIVE domain).
786              
787             =cut
788              
789             sub get_master($)
790             {
791 0     0 1   my $self = shift;
792 0           my $domain = shift;
793 0           my $master = '';
794              
795 0           my $sth = $self->{'dbh'}->prepare("SELECT master FROM domains WHERE name = ?");
796 0           $sth->execute($$domain);
797              
798 0           ($master) = $sth->fetchrow_array;
799 0           return $master;
800             }
801              
802             =head2 increment_serial(\$domain)
803              
804             Increments the serial in the SOA by one.
805             Assumes the serial is an eight digit date (YYYYMMDD) followed by a two digit increment.
806             Expects one scalar reference which is the domain name to update.
807             Returns 1 upon succes and 0 otherwise.
808              
809             =cut
810              
811             sub increment_serial($)
812             {
813 0     0 1   my $self = shift;
814 0           my $domain = shift;
815              
816             # Get a server lock to avoid race condition.
817 0 0         if ( ! $self->_lock )
818             {
819 0           carp("Could not obtain lock.\n");
820 0           return 0;
821             }
822              
823 0           my $sth = $self->{'dbh'}->prepare("SELECT content FROM records WHERE type = 'SOA' AND domain_id = (SELECT id FROM domains WHERE name = ?)");
824 0 0         unless ( $sth->execute($$domain) )
825             {
826 0           $self->_unlock;
827 0           return 0;
828             }
829              
830             # Grab and split SOA into parts.
831 0           my $soa = $sth->fetchrow_array;
832              
833 0 0         unless ($soa)
834 0           { return 0; }
835            
836 0           my @soa = split / / , $soa;
837 0           my $soa_date = substr($soa[2],0,8);
838 0           my $now_date = `date +%Y%m%d`;
839 0           chomp $now_date;
840 0           my $soa_counter = substr($soa[2],-2);
841              
842 0 0         if ( $soa_date != $now_date )
843             {
844 0           $soa[2] = $now_date . '00';
845             }
846             else
847             {
848 0           $soa_counter++;
849 0           $soa_counter %= 100;
850 0           $soa[2] = $now_date . sprintf('%02d',$soa_counter);
851             }
852              
853 0           my $new_soa = join ' ' , @soa;
854              
855 0           $sth = $self->{'dbh'}->prepare("UPDATE records SET content = ? WHERE type = 'SOA' AND domain_id = (SELECT id FROM domains WHERE name = ?)");
856 0 0         if ( $sth->execute($new_soa,$$domain) <= 0 )
857             {
858 0           $self->_unlock;
859 0           return 0;
860             }
861              
862 0           $self->_unlock;
863 0           return 1;
864             }
865              
866             1;
867              
868             =head1 EXAMPLES
869              
870             my $params = { db_user => 'root',
871             db_pass => '',
872             db_name => 'pdns',
873             db_port => '3306',
874             db_host => 'localhost',
875             mysql_print_error => 1,
876             mysql_warn => 1,
877             mysql_auto_commit => 1,
878             mysql_auto_reconnect => 1,
879             };
880              
881             my $pdns = PowerDNS::Backend::MySQL->new($params);
882              
883             my $domain = 'example.com';
884             my $master = '127.0.0.1';
885              
886             unless ( $pdns->add_domain(\$domain) )
887             { print "Could not add domain : $domain \n"; }
888              
889             unless ( $pdns->add_master(\$domain) )
890             { print "Could not add master domain : $domain \n"; }
891              
892             unless ( $pdns->add_slave(\$domain,\$master) )
893             { print "Could not add slave domain : $domain \n"; }
894              
895             unless ( $pdns->delete_domain(\$domain) )
896             { print "Could not delete domain : $domain \n"; }
897              
898             my $domain_names = $pdns->list_domain_names;
899              
900             for my $domain (@$domain_names)
901             { print "$domain \n"; }
902              
903             my $type = 'NATIVE';
904             my $domain_names = $pdns->list_domain_names_by_type(\$type);
905              
906             for my $domain (@$domain_names)
907             { print "$domain \n"; }
908              
909             my $master = '127.0.0.1';
910             my $domain_names = $pdns->list_slave_domain_names(\$master);
911              
912             for my $domain (@$domain_names)
913             { print "$domain \n"; }
914            
915             if ( $pdns->domain_exists(\$domain) )
916             { print "The domain $domain does exist. \n"; }
917             else
918             { print "The domain $domain does NOT exist. \n"; }
919            
920             my $rr = 'CNAME';
921             my $records = $pdns->list_records(\$rr , \$domain);
922             for my $record (@$records)
923             { print "@$record\n"; }
924            
925             my @rr = ('www.example.com','CNAME','example.com');
926             unless ( $pdns->add_record( \@rr , \$domain) )
927             { print "Could not add a RR for $domain \n"; }
928            
929             unless ( $pdns->delete_record(\@rr , \$domain) )
930             { print "Could not delete RR for $domain \n"; }
931            
932             my $domain = 'example.com';
933             my @rr1 = ('localhost.example.com','A','127.0.0.1');
934             my @rr2 = ('localhost.example.com','CNAME','example.com');
935            
936             unless ( $pdns->update_record(\@rr1 , \@rr2 , \$domain) )
937             { print "Update failed for $domain . \n"; }
938              
939             my (@rr1,@rr2,$domain);
940              
941             @rr1 = ('example.com','MX');
942             @rr2 = ('example.com','MX','mx.example.com');
943             $domain = 'example.com';
944              
945             unless ( $pdns->update_records( \@rr1 , \@rr2 , \$domain ) )
946             { print "Update failed for $domain . \n"; }
947              
948             @rr1 = ('example.com','MX');
949             @rr2 = ('example.com','MX','mx.example.com');
950             $domain = 'example.com';
951              
952             unless ( $pdns->update_or_add_records(\@rr1,\@rr2,\$domain) )
953             { print "Could not update/add record.\n"; }
954            
955             my $domain = 'example.com';
956             my $content = 'localhost.example.com';
957             my $records = $pdns->find_record_by_content(\$content , \$domain);
958             my ($name , $type) = @$records;
959             print "Name: $name\n";
960             print "Type: $type\n";
961              
962             my $domain = 'example.com';
963             my $name = 'localhost.example.com';
964             my $records = $pdns->find_record_by_name(\$name, \$domain);
965             my ($content, $type) = @$records;
966             print "Content: $content\n";
967             print "Type: $type\n";
968              
969             my $domain = 'example.com';
970             $pdns->make_domain_native(\$domain);
971              
972             my $domain = 'example.com';
973             $pdns->make_domain_master(\$domain);
974              
975             my $domain = 'example.com';
976             my $type = $pdns->get_domain_type(\$domain);
977             if ( $type )
978             { print "Type is '$type'\n"; }
979             else
980             { print "Domain $domain does not exist.\n" }
981              
982             my $master = $pdns->get_master(\$domain);
983             print "Master: $master\n";
984              
985             my $domain = 'augnix.net';
986             unless ( $pdns->increment_serial(\$domain) )
987             { print "Could not increment serial."; }
988              
989             =head1 NOTES
990              
991             Because PowerDNS::Backend::MySQL uses DBI you can get the last DBI error from the
992             global variable "$DBI::errstr"; this can be handy when you want more details as to
993             why a particular method failed.
994              
995             =head1 AUTHOR
996              
997             Augie Schwer, C<< >>
998              
999             http://www.schwer.us
1000              
1001             =head1 BUGS
1002              
1003             Please report any bugs or feature requests to
1004             C, or through the web interface at
1005             L.
1006             I will be notified, and then you'll automatically be notified of progress on
1007             your bug as I make changes.
1008              
1009             =head1 SUPPORT
1010              
1011             You can find documentation for this module with the perldoc command.
1012              
1013             perldoc PowerDNS::Backend::MySQL
1014              
1015             You can also look for information at:
1016              
1017             =over 4
1018              
1019             =item * AnnoCPAN: Annotated CPAN documentation
1020              
1021             L
1022              
1023             =item * CPAN Ratings
1024              
1025             L
1026              
1027             =item * RT: CPAN's request tracker
1028              
1029             L
1030              
1031             =item * Search CPAN
1032              
1033             L
1034              
1035             =item * Github
1036              
1037             L
1038              
1039             =back
1040              
1041             =head1 ACKNOWLEDGEMENTS
1042              
1043             I would like to thank Sonic.net for allowing me to release this to the public.
1044              
1045             =head1 COPYRIGHT & LICENSE
1046              
1047             Copyright 2012 Augie Schwer, all rights reserved.
1048              
1049             This program is free software; you can redistribute it and/or modify it
1050             under the same terms as Perl itself.
1051              
1052             =head1 VERSION
1053              
1054             0.12
1055              
1056             =cut
1057