Pascal Dreissen
2016-07-08 f1193b43f4c9fd132741d30f03f0b35841011989
commit | author | age
94ffe9 1 <?php
T 2
3 /*
4 Copyright (c) 2009, Falko Timme, Till Brehm, projektfarm Gmbh
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /*
32 The powerdns database name has to be "powerdns" and it must be accessible
33 by the "ispconfig" database user
34
35 TABLE STRUCTURE of the "powerdns" database:
36
37 CREATE TABLE `domains` (
38   `id` int(11) NOT NULL auto_increment,
39   `name` varchar(255) NOT NULL,
40   `master` varchar(128) default NULL,
41   `last_check` int(11) default NULL,
42   `type` varchar(6) NOT NULL,
43   `notified_serial` int(11) default NULL,
44   `account` varchar(40) default NULL,
45   `ispconfig_id` int(11) NOT NULL,
46   PRIMARY KEY  (`id`),
47   UNIQUE KEY `name_index` (`name`)
48 ) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
49
50 CREATE TABLE `records` (
51   `id` int(11) NOT NULL auto_increment,
52   `domain_id` int(11) default NULL,
53   `name` varchar(255) default NULL,
54   `type` varchar(6) default NULL,
55   `content` varchar(255) default NULL,
56   `ttl` int(11) default NULL,
57   `prio` int(11) default NULL,
58   `change_date` int(11) default NULL,
59   `ispconfig_id` int(11) NOT NULL,
60   PRIMARY KEY  (`id`),
61   KEY `rec_name_index` (`name`),
62   KEY `nametype_index` (`name`,`type`),
63   KEY `domain_id` (`domain_id`)
64 ) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
65
66 CREATE TABLE `supermasters` (
67   `ip` varchar(25) NOT NULL,
68   `nameserver` varchar(255) NOT NULL,
69   `account` varchar(40) default NULL
70 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
71
72
73 IMPORTANT:
74 - This plugin does not support ALIAS records (supported only by MyDNS).
75
76 TODO:
77 - introduce a variable for the PowerDNS database
78 */
79
80 class powerdns_plugin {
b1a6a5 81
94ffe9 82     var $plugin_name = 'powerdns_plugin';
T 83     var $class_name  = 'powerdns_plugin';
b1a6a5 84
94ffe9 85     //* This function is called during ispconfig installation to determine
T 86     //  if a symlink shall be created for this plugin.
87     function onInstall() {
88         global $conf;
b1a6a5 89
94ffe9 90         if(isset($conf['powerdns']['installed']) && $conf['powerdns']['installed'] == true) {
T 91             return true;
92         } else {
93             return false;
94         }
b1a6a5 95
94ffe9 96     }
b1a6a5 97
MC 98
94ffe9 99     /*
T 100          This function is called when the plugin is loaded
101     */
b1a6a5 102
94ffe9 103     function onLoad() {
T 104         global $app;
b1a6a5 105
94ffe9 106         /*
T 107         Register for the events
108         */
b1a6a5 109
94ffe9 110         //* SOA
b1a6a5 111         $app->plugins->registerEvent('dns_soa_insert', $this->plugin_name, 'soa_insert');
MC 112         $app->plugins->registerEvent('dns_soa_update', $this->plugin_name, 'soa_update');
113         $app->plugins->registerEvent('dns_soa_delete', $this->plugin_name, 'soa_delete');
a59731 114
D 115         //* SLAVE
b1a6a5 116         $app->plugins->registerEvent('dns_slave_insert', $this->plugin_name, 'slave_insert');
MC 117         $app->plugins->registerEvent('dns_slave_update', $this->plugin_name, 'slave_update');
118         $app->plugins->registerEvent('dns_slave_delete', $this->plugin_name, 'slave_delete');
119
94ffe9 120         //* RR
b1a6a5 121         $app->plugins->registerEvent('dns_rr_insert', $this->plugin_name, 'rr_insert');
MC 122         $app->plugins->registerEvent('dns_rr_update', $this->plugin_name, 'rr_update');
123         $app->plugins->registerEvent('dns_rr_delete', $this->plugin_name, 'rr_delete');
124
94ffe9 125     }
b1a6a5 126
MC 127
128     function soa_insert($event_name, $data) {
94ffe9 129         global $app, $conf;
b1a6a5 130
94ffe9 131         if($data["new"]["active"] != 'Y') return;
b1a6a5 132
94ffe9 133         $origin = substr($data["new"]["origin"], 0, -1);
T 134         $ispconfig_id = $data["new"]["id"];
cc7a82 135         $serial = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $ispconfig_id);
c03252 136         $serial_id = $serial["serial"];
cc7a82 137         $app->db->query("INSERT INTO powerdns.domains (name, type, notified_serial, ispconfig_id) VALUES (?, ?, ?, ?)", $origin, 'MASTER', $serial_id, $ispconfig_id);
4bd960 138         $zone_id = $app->db->insertID();
94ffe9 139         if(substr($data["new"]["ns"], -1) == '.'){
T 140             $ns = substr($data["new"]["ns"], 0, -1);
141         } else {
142             $ns = $data["new"]["ns"].'.'.$origin;
143         }
144         if($ns == '') $ns = $origin;
b1a6a5 145
94ffe9 146         $hostmaster = substr($data["new"]["mbox"], 0, -1);
38184e 147         $content = $ns.' '.$hostmaster.' '.$data["new"]["serial"].' '.$data["new"]["refresh"].' '.$data["new"]["retry"].' '.$data["new"]["expire"].' '.$data["new"]["minimum"];
94ffe9 148         $ttl = $data["new"]["ttl"];
b1a6a5 149
cc7a82 150         $app->db->query("INSERT INTO powerdns.records (domain_id, name, type, content, ttl, prio, change_date, ispconfig_id) VALUES (?, ?, 'SOA', ?, ?, 0, UNIX_TIMESTAMP(), ?)", $zone_id, $origin, $content, $ttl, $ispconfig_id);
b1a6a5 151
38184e 152         //* tell pdns to rediscover zones in DB
CS 153         $this->zoneRediscover();
154         //* tell pdns to use 'pdnssec rectify' on the new zone
155         $this->rectifyZone($data);
156         //* tell pdns to send notify to slave
157         $this->notifySlave($data);
94ffe9 158     }
b1a6a5 159
MC 160     function soa_update($event_name, $data) {
94ffe9 161         global $app, $conf;
b1a6a5 162
94ffe9 163         if($data["new"]["active"] != 'Y'){
T 164             if($data["old"]["active"] != 'Y') return;
b1a6a5 165             $this->soa_delete($event_name, $data);
94ffe9 166         } else {
cc7a82 167             $exists = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ?", $data["new"]["id"]);
0614b5 168             if($data["old"]["active"] == 'Y' && is_array($exists)){
94ffe9 169                 $origin = substr($data["new"]["origin"], 0, -1);
T 170                 $ispconfig_id = $data["new"]["id"];
b1a6a5 171
94ffe9 172                 if(substr($data["new"]["ns"], -1) == '.'){
T 173                     $ns = substr($data["new"]["ns"], 0, -1);
174                 } else {
175                     $ns = $data["new"]["ns"].'.'.$origin;
176                 }
177                 if($ns == '') $ns = $origin;
b1a6a5 178
94ffe9 179                 $hostmaster = substr($data["new"]["mbox"], 0, -1);
38184e 180                 $content = $ns.' '.$hostmaster.' '.$data["new"]["serial"].' '.$data["new"]["refresh"].' '.$data["new"]["retry"].' '.$data["new"]["expire"].' '.$data["new"]["minimum"];
94ffe9 181                 $ttl = $data["new"]["ttl"];
cc7a82 182                 $app->db->query("UPDATE powerdns.records SET name = ?, content = ?, ttl = ?, change_date = UNIX_TIMESTAMP() WHERE ispconfig_id = ? AND type = 'SOA'", $origin, $content, $ttl, $data["new"]["id"]);
a59731 183
38184e 184                 //* tell pdns to use 'pdnssec rectify' on the new zone
CS 185                 $this->rectifyZone($data);
186                 //* tell pdns to send notify to slave
187                 $this->notifySlave($data);
94ffe9 188             } else {
b1a6a5 189                 $this->soa_insert($event_name, $data);
94ffe9 190                 $ispconfig_id = $data["new"]["id"];
cc7a82 191                 if($records = $app->db->queryAllRecords("SELECT * FROM dns_rr WHERE zone = ? AND active = 'Y'", $ispconfig_id)){
94ffe9 192                     foreach($records as $record){
T 193                         foreach($record as $key => $val){
194                             $data["new"][$key] = $val;
195                         }
196                         $this->rr_insert("dns_rr_insert", $data);
197                     }
198                 }
38184e 199                 //* tell pdns to use 'pdnssec rectify' on the new zone
CS 200                 $this->rectifyZone($data);
201                 //* tell pdns to send notify to slave
202                 $this->notifySlave($data);
94ffe9 203             }
T 204         }
205     }
b1a6a5 206
MC 207     function soa_delete($event_name, $data) {
94ffe9 208         global $app, $conf;
b1a6a5 209
cc7a82 210         $zone = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ? AND type = 'MASTER'", $data["old"]["id"]);
94ffe9 211         $zone_id = $zone["id"];
cc7a82 212         $app->db->query("DELETE FROM powerdns.records WHERE domain_id = ?", $zone_id);
MC 213         $app->db->query("DELETE FROM powerdns.domains WHERE id = ?", $zone_id);
a59731 214     }
D 215
b1a6a5 216     function slave_insert($event_name, $data) {
a59731 217         global $app, $conf;
b1a6a5 218
a59731 219         if($data["new"]["active"] != 'Y') return;
b1a6a5 220
a59731 221         $origin = substr($data["new"]["origin"], 0, -1);
D 222         $ispconfig_id = $data["new"]["id"];
223         $master_ns = $data["new"]["ns"];
b1a6a5 224
cc7a82 225         $app->db->query("INSERT INTO powerdns.domains (name, type, master, ispconfig_id) VALUES (?, ?, ?, ?)", $origin, 'SLAVE', $master_ns, $ispconfig_id);
a59731 226
4bd960 227         $zone_id = $app->db->insertID();
a59731 228
38184e 229         //* tell pdns to fetch zone from master server
CS 230         $this->fetchFromMaster($data);
a59731 231     }
b1a6a5 232
MC 233     function slave_update($event_name, $data) {
a59731 234         global $app, $conf;
b1a6a5 235
a59731 236         if($data["new"]["active"] != 'Y'){
D 237             if($data["old"]["active"] != 'Y') return;
b1a6a5 238             $this->slave_delete($event_name, $data);
a59731 239         } else {
D 240             if($data["old"]["active"] == 'Y'){
b1a6a5 241
MC 242                 $origin = substr($data["new"]["origin"], 0, -1);
243                 $ispconfig_id = $data["new"]["id"];
244                 $master_ns = $data["new"]["ns"];
245
cc7a82 246                 $app->db->query("UPDATE powerdns.domains SET name = ?, type = 'SLAVE', master = ? WHERE ispconfig_id=? AND type = 'SLAVE'", $origin, $master_ns, $ispconfig_id);
b1a6a5 247                 $zone_id = $app->db->insertID();
a59731 248
cc7a82 249                 $zone = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ? AND type = 'SLAVE'", $ispconfig_id);
b1a6a5 250                 $zone_id = $zone["id"];
cc7a82 251                 $app->db->query("DELETE FROM powerdns.records WHERE domain_id = ? AND ispconfig_id = 0", $zone_id);
a59731 252
38184e 253                 //* tell pdns to fetch zone from master server
CS 254                 $this->fetchFromMaster($data);
b1a6a5 255
a59731 256             } else {
b1a6a5 257                 $this->slave_insert($event_name, $data);
a59731 258
D 259             }
260         }
b1a6a5 261
a59731 262     }
b1a6a5 263
MC 264     function slave_delete($event_name, $data) {
a59731 265         global $app, $conf;
b1a6a5 266
cc7a82 267         $zone = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ? AND type = 'SLAVE'", $data["old"]["id"]);
a59731 268         $zone_id = $zone["id"];
cc7a82 269         $app->db->query("DELETE FROM powerdns.records WHERE domain_id = ?", $zone_id);
MC 270         $app->db->query("DELETE FROM powerdns.domains WHERE id = ?", $zone_id);
94ffe9 271     }
b1a6a5 272
MC 273     function rr_insert($event_name, $data) {
94ffe9 274         global $app, $conf;
T 275         if($data["new"]["active"] != 'Y') return;
cc7a82 276         $exists = $app->db->queryOneRecord("SELECT * FROM powerdns.records WHERE ispconfig_id = ?", $data["new"]["id"]);
0614b5 277         if ( is_array($exists) ) return;
b1a6a5 278
cc7a82 279         $zone = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $data["new"]["zone"]);
94ffe9 280         $origin = substr($zone["origin"], 0, -1);
cc7a82 281         $powerdns_zone = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ? AND type = 'MASTER'", $data["new"]["zone"]);
94ffe9 282         $zone_id = $powerdns_zone["id"];
b1a6a5 283
94ffe9 284         $type = $data["new"]["type"];
b1a6a5 285
1a0213 286         if(substr($data["new"]["name"], -1) == '.'){
W 287             $name = substr($data["new"]["name"], 0, -1);
288         } else {
289             if($data["new"]["name"] == ""){
290                 $name = $origin;
291             } else {
292                 $name = $data["new"]["name"].'.'.$origin;
293             }
94ffe9 294         }
1a0213 295         if($name == '') $name = $origin;
b1a6a5 296
94ffe9 297         switch ($type) {
b1a6a5 298         case "CNAME":
MC 299         case "MX":
300         case "NS":
301         case "ALIAS":
302         case "PTR":
303         case "SRV":
304             if(substr($data["new"]["data"], -1) == '.'){
305                 $content = substr($data["new"]["data"], 0, -1);
306             } else {
307                 $content = $data["new"]["data"].'.'.$origin;
308             }
309             break;
310         case "HINFO":
311             $content = $data["new"]["data"];
312             $quote1 = strpos($content, '"');
313             if($quote1 !== FALSE){
314                 $quote2 = strpos(substr($content, ($quote1 + 1)), '"');
315             }
316             if($quote1 !== FALSE && $quote2 !== FALSE){
317                 $text_between_quotes = str_replace(' ', '_', substr($content, ($quote1 + 1), (($quote2 - $quote1))));
318                 $content = $text_between_quotes.substr($content, ($quote2 + 2));
319             }
320             break;
321         default:
322             $content = $data["new"]["data"];
94ffe9 323         }
b1a6a5 324
94ffe9 325         $ttl = $data["new"]["ttl"];
T 326         $prio = $data["new"]["aux"];
327         $change_date = time();
328         $ispconfig_id = $data["new"]["id"];
b1a6a5 329
cc7a82 330         $app->db->query("INSERT INTO powerdns.records (domain_id, name, type, content, ttl, prio, change_date, ispconfig_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", $zone_id, $name, $type, $content, $ttl, $prio, $change_date, $ispconfig_id);
b1a6a5 331
38184e 332         //* tell pdns to use 'pdnssec rectify' on the new zone
CS 333         $this->rectifyZone($data);
94ffe9 334     }
b1a6a5 335
MC 336     function rr_update($event_name, $data) {
94ffe9 337         global $app, $conf;
b1a6a5 338
94ffe9 339         if($data["new"]["active"] != 'Y'){
T 340             if($data["old"]["active"] != 'Y') return;
b1a6a5 341             $this->rr_delete($event_name, $data);
94ffe9 342         } else {
cc7a82 343             $exists = $app->db->queryOneRecord("SELECT * FROM powerdns.records WHERE ispconfig_id = ?", $data["new"]["id"]);
0614b5 344             if($data["old"]["active"] == 'Y' && is_array($exists)){
cc7a82 345                 $zone = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $data["new"]["zone"]);
94ffe9 346                 $origin = substr($zone["origin"], 0, -1);
cc7a82 347                 $powerdns_zone = $app->db->queryOneRecord("SELECT * FROM powerdns.domains WHERE ispconfig_id = ? AND type = 'MASTER'", $data["new"]["zone"]);
94ffe9 348                 $zone_id = $powerdns_zone["id"];
b1a6a5 349
MC 350                 $type = $data["new"]["type"];
351
c5d25f 352                 if(substr($data["new"]["name"], -1) == '.'){
W 353                     $name = substr($data["new"]["name"], 0, -1);
354                 } else {
355                     if($data["new"]["name"] == ""){
356                         $name = $origin;
357                     } else {
358                         $name = $data["new"]["name"].'.'.$origin;
359                     }
94ffe9 360                 }
c5d25f 361                 if($name == '') $name = $origin;
94ffe9 362
T 363                 switch ($type) {
b1a6a5 364                 case "CNAME":
MC 365                 case "MX":
366                 case "NS":
367                 case "ALIAS":
368                 case "PTR":
369                 case "SRV":
370                     if(substr($data["new"]["data"], -1) == '.'){
371                         $content = substr($data["new"]["data"], 0, -1);
372                     } else {
373                         $content = $data["new"]["data"].'.'.$origin;
374                     }
375                     break;
376                 case "HINFO":
377                     $content = $data["new"]["data"];
378                     $quote1 = strpos($content, '"');
379                     if($quote1 !== FALSE){
380                         $quote2 = strpos(substr($content, ($quote1 + 1)), '"');
381                     }
382                     if($quote1 !== FALSE && $quote2 !== FALSE){
383                         $text_between_quotes = str_replace(' ', '_', substr($content, ($quote1 + 1), (($quote2 - $quote1))));
384                         $content = $text_between_quotes.substr($content, ($quote2 + 2));
385                     }
386                     break;
387                 default:
388                     $content = $data["new"]["data"];
94ffe9 389                 }
b1a6a5 390
94ffe9 391                 $ttl = $data["new"]["ttl"];
T 392                 $prio = $data["new"]["aux"];
393                 $change_date = time();
394                 $ispconfig_id = $data["new"]["id"];
cc7a82 395                 $app->db->query("UPDATE powerdns.records SET name = ?, type = ?, content = ?, ttl = ?, prio = ?, change_date = UNIX_TIMESTAMP() WHERE ispconfig_id = ? AND type != 'SOA'", $name, $type, $content, $ttl, $prio, $ispconfig_id);
b1a6a5 396
38184e 397                 //* tell pdns to use 'pdnssec rectify' on the new zone
CS 398                 $this->rectifyZone($data);
94ffe9 399             } else {
b1a6a5 400                 $this->rr_insert($event_name, $data);
94ffe9 401             }
T 402         }
403     }
b1a6a5 404
MC 405     function rr_delete($event_name, $data) {
94ffe9 406         global $app, $conf;
b1a6a5 407
94ffe9 408         $ispconfig_id = $data["old"]["id"];
cc7a82 409         $app->db->query("DELETE FROM powerdns.records WHERE ispconfig_id = ? AND type != 'SOA'", $ispconfig_id);
94ffe9 410     }
b1a6a5 411
38184e 412     function find_pdns_control() {
CS 413         $output = array();
414         $retval = '';
415         exec("type -p pdns_control", $output, $retval);
416         if ($retval == 0 && is_file($output[0])){
417             return $output[0];
418         } else {
419             return false;
420         }
421     }
422
423     function find_pdns_pdnssec() {
424         $output = array();
425         $retval = '';
426         exec("type -p pdnssec", $output, $retval);
427         if ($retval == 0 && is_file($output[0])){
428             return $output[0];
429         } else {
430             return false;
431         }
432     }
433
434     function zoneRediscover() {
435         $pdns_control = $this->find_pdns_control();
436         if ( $pdns_control != false ) {
437             exec($pdns_control . ' rediscover');
438         }
439     }
440
441     function notifySlave($data) {
442         $pdns_control = $this->find_pdns_control();
443         if ( $pdns_control != false ) {
444             exec($pdns_control . ' notify ' . rtrim($data["new"]["origin"],"."));
445         }
446     }
447
448     function fetchFromMaster($data) {
449         $pdns_control = $this->find_pdns_control();
450         if ( $pdns_control != false ) {
451             exec($pdns_control . ' retrieve ' . rtrim($data["new"]["origin"],"."));
452         }
453     }
454
455     function get_pdns_version() {
456         $pdns_control = $this->find_pdns_control();
457         if ( $pdns_control != false ) {
458             $output=array();
459             $retval='';
460             exec($pdns_control . ' version',$output,$retval);
461             return $output[0];
462         } else {
463             //* fallback to version 2
464             return 2;
465         }
466     }
467
468     function rectifyZone($data) {
469         global $app, $conf;
470         if ( preg_match('/^3/',$this->get_pdns_version()) ) {
471             $pdns_pdnssec = $this->find_pdns_pdnssec();
472             if ( $pdns_pdnssec != false ) {
473                 if (isset($data["new"]["origin"])) {
474                     //* data has origin field only for SOA recordtypes
475                     exec($pdns_pdnssec . ' rectify-zone ' . rtrim($data["new"]["origin"],"."));
476                 } else {
477                     // get origin from DB for all other recordtypes
cc7a82 478                     $zn = $app->db->queryOneRecord("SELECT d.name AS name FROM powerdns.domains d, powerdns.records r WHERE r.ispconfig_id=? AND r.domain_id = d.id", $data["new"]["id"]);
38184e 479                     exec($pdns_pdnssec . ' rectify-zone ' . trim($zn["name"]));
CS 480                 }
481             }
482         }
483     }
b1a6a5 484
94ffe9 485 } // end class
T 486
a59731 487 ?>