bind_plugin.inc.php 23.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

/*
Copyright (c) 2009, Till Brehm, projektfarm Gmbh
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of ISPConfig nor the names of its contributors
      may be used to endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

class bind_plugin {
32

33
34
35
	var $plugin_name = 'bind_plugin';
	var $class_name  = 'bind_plugin';
	var $action = 'update';
36

37
38
39
40
	//* This function is called during ispconfig installation to determine
	//  if a symlink shall be created for this plugin.
	function onInstall() {
		global $conf;
41

42
		if(isset($conf['bind']['installed']) && $conf['bind']['installed'] == true && @is_link('/usr/local/ispconfig/server/mods-enabled/dns_module.inc.php')) {
43
44
45
46
			return true;
		} else {
			return false;
		}
47

48
	}
49
50


51
52
53
	/*
	 	This function is called when the plugin is loaded
	*/
54

55
56
	function onLoad() {
		global $app;
57

58
59
60
		/*
		Register for the events
		*/
61

62
		//* SOA
63
64
65
66
67
68
69
70
71
		$app->plugins->registerEvent('dns_soa_insert', $this->plugin_name, 'soa_insert');
		$app->plugins->registerEvent('dns_soa_update', $this->plugin_name, 'soa_update');
		$app->plugins->registerEvent('dns_soa_delete', $this->plugin_name, 'soa_delete');

		//* SLAVE
		$app->plugins->registerEvent('dns_slave_insert', $this->plugin_name, 'slave_insert');
		$app->plugins->registerEvent('dns_slave_update', $this->plugin_name, 'slave_update');
		$app->plugins->registerEvent('dns_slave_delete', $this->plugin_name, 'slave_delete');

72
		//* RR
73
74
75
76
		$app->plugins->registerEvent('dns_rr_insert', $this->plugin_name, 'rr_insert');
		$app->plugins->registerEvent('dns_rr_update', $this->plugin_name, 'rr_update');
		$app->plugins->registerEvent('dns_rr_delete', $this->plugin_name, 'rr_delete');

77
	}
78

79
	//* This creates DNSSEC-Keys and calls soa_dnssec_update.
80
	function soa_dnssec_create(&$data) {
81
82
83
84
85
86
87
		global $app, $conf;

		//* Load libraries
		$app->uses("getconf,tpl");

		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
88

89
		$domain = substr($data['new']['origin'], 0, strlen($data['new']['origin'])-1);
Helmo's avatar
Helmo committed
90
		if (!file_exists($dns_config['bind_zonefiles_dir'].'/'.$this->zone_file_prefix().$domain)) return false;
91

92
		//* Check Entropy
Till Brehm's avatar
Till Brehm committed
93
		if (file_get_contents('/proc/sys/kernel/random/entropy_avail') < 200) {
A. Täffner's avatar
oops    
A. Täffner committed
94
95
			$app->log('DNSSEC ERROR: We are low on entropy. Not generating new Keys for '.$domain.'. Please consider installing package haveged.', LOGLEVEL_WARN);
			echo "DNSSEC ERROR: We are low on entropy. Not generating new Keys for $domain. Please consider installing package haveged.\n";
96
97
			return false;
		}
98

99
		//* Verify that we do not already have keys (overwriting-protection)
100
		if($data['old']['dnssec_algo'] == $data['new']['dnssec_algo']) {
Helmo's avatar
Helmo committed
101
			if (file_exists($dns_config['bind_keyfiles_dir'].'/dsset-'.$domain.'.')) {
102
103
104
				return $this->soa_dnssec_update($data);
			} else if ($data['new']['dnssec_initialized'] == 'Y') { //In case that we generated keys but the dsset-file was not generated
				$keycount=0;
Helmo's avatar
Helmo committed
105
				foreach (glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'*.key') as $keyfile) {
106
107
108
109
110
111
					$keycount++;
				}
				if ($keycount > 0) {
					$this->soa_dnssec_sign($data);
					return true;
				}
112
			}
113
		}
114

Till Brehm's avatar
Till Brehm committed
115
116
		// Get DNSSEC Algorithms
		$dnssec_algo = explode(',',$data['new']['dnssec_algo']);
117

118
		//* Create the Zone Signing and Key Signing Keys
Helmo's avatar
Helmo committed
119
120
		if(in_array('ECDSAP256SHA256',$dnssec_algo) && count(glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+013*.key')) == 0) {
			$app->system->exec_safe('cd ?; dnssec-keygen -3 -a ECDSAP256SHA256 -n ZONE ?; dnssec-keygen -f KSK -3 -a ECDSAP256SHA256 -n ZONE ?', $dns_config['bind_keyfiles_dir'], $domain, $domain);
Till Brehm's avatar
Till Brehm committed
121
		}
Helmo's avatar
Helmo committed
122
123
		if(in_array('NSEC3RSASHA1',$dnssec_algo) && count(glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+007*.key')) == 0) {
			$app->system->exec_safe('cd ?; dnssec-keygen -a NSEC3RSASHA1 -b 2048 -n ZONE ?; dnssec-keygen -f KSK -a NSEC3RSASHA1 -b 4096 -n ZONE ?', $dns_config['bind_keyfiles_dir'], $domain, $domain);
124
		}
125

126
127
128
		$this->soa_dnssec_sign($data); //Now sign the zone for the first time
		$data['new']['dnssec_initialized']='Y';
	}
129

130
131
	function soa_dnssec_sign(&$data) {
		global $app, $conf;
132

133
134
135
136
137
		//* Load libraries
		$app->uses("getconf,tpl");

		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
138

Helmo's avatar
Helmo committed
139
		$filespre = $this->zone_file_prefix();
140
141
		$domain = substr($data['new']['origin'], 0, strlen($data['new']['origin'])-1);
		if (!file_exists($dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain)) return false;
142

143
144
		//* Get DNSSEC Algorithms
		$dnssec_algo = explode(',',$data['new']['dnssec_algo']);
145

146
		//* Get Zone file content
147
148
		$zonefile = file_get_contents($dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain);
		$keycount=0;
149

150
151
		//* Include ECDSAP256SHA256 keys in zone
		if(in_array('ECDSAP256SHA256',$dnssec_algo)) {
Helmo's avatar
Helmo committed
152
153
			foreach (glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+013*.key') as $keyfile) {
				$includeline = '$INCLUDE ' . $keyfile;
154
155
156
157
				if (!preg_match('@'.preg_quote($includeline).'@', $zonefile)) $zonefile .= "\n".$includeline."\n";
				$keycount++;
			}
		}
158

159
160
		//* Include NSEC3RSASHA1 keys in zone
		if(in_array('NSEC3RSASHA1',$dnssec_algo)) {
Helmo's avatar
Helmo committed
161
162
			foreach (glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+007*.key') as $keyfile) {
				$includeline = '$INCLUDE ' . $keyfile;
163
164
165
				if (!preg_match('@'.preg_quote($includeline).'@', $zonefile)) $zonefile .= "\n".$includeline."\n";
				$keycount++;
			}
166
		}
167

Till Brehm's avatar
Till Brehm committed
168
		$keycount_wanted = count(explode(',',$data['new']['dnssec_algo']))*2;
169

170
		if ($keycount != $keycount_wanted) $app->log('DNSSEC Warning: There are more or less than 2 keyfiles for each algorithm for zone '.$domain.'. Found: '.$keycount. ' Expected: '.$keycount_wanted, LOGLEVEL_WARN);
171
		file_put_contents($dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain, $zonefile);
172

173
		//* Sign the zone and set it valid for max. 16 days
Helmo's avatar
Helmo committed
174
		$app->system->exec_safe('cd ?; dnssec-signzone -A -e +1382400 -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) -N increment -o ? -K ? -t ?', $dns_config['bind_zonefiles_dir'], $domain, $dns_config['bind_keyfiles_dir'], $dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain);
175

176
		//* Write Data back ino DB
Helmo's avatar
Helmo committed
177
		$dnssecdata = "DS-Records:\n".file_get_contents($dns_config['bind_keyfiles_dir'].'/dsset-'.$domain.'.');
178
		$dnssecdata .= "\n------------------------------------\n\nDNSKEY-Records:\n";
179

180
		if(in_array('ECDSAP256SHA256',$dnssec_algo)) {
Helmo's avatar
Helmo committed
181
			foreach (glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+013*.key') as $keyfile) {
182
183
184
				$dnssecdata .= file_get_contents($keyfile)."\n\n";
			}
		}
185

186
		if(in_array('NSEC3RSASHA1',$dnssec_algo)) {
Helmo's avatar
Helmo committed
187
			foreach (glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+007*.key') as $keyfile) {
188
189
				$dnssecdata .= file_get_contents($keyfile)."\n\n";
			}
190
		}
191

A. Täffner's avatar
A. Täffner committed
192
193
		if ($app->dbmaster !== $app->db) $app->dbmaster->query('UPDATE dns_soa SET dnssec_info=?, dnssec_initialized=\'Y\', dnssec_last_signed=? WHERE id=?', $dnssecdata, intval(time()), intval($data['new']['id']));
		$app->db->query('UPDATE dns_soa SET dnssec_info=?, dnssec_initialized=\'Y\', dnssec_last_signed=? WHERE id=?', $dnssecdata, intval(time()), intval($data['new']['id']));
194
	}
195

196
	function soa_dnssec_update(&$data, $new=false) {
197
198
199
200
201
202
203
		global $app, $conf;

		//* Load libraries
		$app->uses("getconf,tpl");

		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
204

Helmo's avatar
Helmo committed
205
		$filespre = $this->zone_file_prefix();
A. Täffner's avatar
A. Täffner committed
206
207
		$domain = substr($data['new']['origin'], 0, strlen($data['new']['origin'])-1);
		if (!file_exists($dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain)) return false;
208

A. Täffner's avatar
A. Täffner committed
209
		//* Check for available entropy
210
		if (file_get_contents('/proc/sys/kernel/random/entropy_avail') < 200) {
Helmo's avatar
Helmo committed
211
			$app->log('DNSSEC ERROR: We are low on entropy. This could cause server script to fail. Please consider installing package haveged.', LOGLEVEL_ERROR);
212
			echo "DNSSEC ERROR: We are low on entropy. This could cause server script to fail. Please consider installing package haveged.\n";
213
214
			return false;
		}
215

Helmo's avatar
Helmo committed
216
		if (!$new && !file_exists($dns_config['bind_keyfiles_dir'].'/dsset-'.$domain.'.')) $this->soa_dnssec_create($data);
217

A. Täffner's avatar
A. Täffner committed
218
		$dbdata = $app->db->queryOneRecord('SELECT id,serial FROM dns_soa WHERE id=?', intval($data['new']['id']));
219
220
		$app->system->exec_safe('cd ?; named-checkzone ? ? | egrep -ho \'[0-9]{10}\'', $dns_config['bind_zonefiles_dir'], $domain, $dns_config['bind_zonefiles_dir'].'/'.$filespre.$domain);
		$retState = $app->system->last_exec_retcode();
A. Täffner's avatar
A. Täffner committed
221
		if ($retState != 0) {
Helmo's avatar
Helmo committed
222
			$app->log('DNSSEC Error: Error in Zonefile for '.$domain, LOGLEVEL_ERROR);
A. Täffner's avatar
A. Täffner committed
223
224
			return false;
		}
225

226
227
		$this->soa_dnssec_sign($data);
	}
228

229
230
231
232
233
234
235
236
	function soa_dnssec_delete(&$data) {
		global $app, $conf;

		//* Load libraries
		$app->uses("getconf,tpl");

		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
237

238
		$domain = substr($data['new']['origin'], 0, strlen($data['new']['origin'])-1);
239

Helmo's avatar
Helmo committed
240
		$key_files = glob($dns_config['bind_keyfiles_dir'].'/K'.$domain.'.+*');
241
242
243
		foreach($key_files as $file) {
			unlink($file);
		}
Helmo's avatar
Helmo committed
244
		unlink($dns_config['bind_zonefiles_dir'].'/'.$this->zone_file_prefix().$domain.'.signed');
Helmo's avatar
Helmo committed
245
		unlink($dns_config['bind_keyfiles_dir'].'/dsset-'.$domain.'.');
246

A. Täffner's avatar
A. Täffner committed
247
248
		if ($app->dbmaster !== $app->db) $app->dbmaster->query('UPDATE dns_soa SET dnssec_info=\'\', dnssec_initialized=\'N\' WHERE id=?', intval($data['new']['id']));
		$app->db->query('UPDATE dns_soa SET dnssec_info=\'\', dnssec_initialized=\'N\' WHERE id=?', intval($data['new']['id']));
249
	}
250
251

	function soa_insert($event_name, $data) {
252
		global $app, $conf;
253

254
		$this->action = 'insert';
255
256
		$this->soa_update($event_name, $data);

257
	}
258
259

	function soa_update($event_name, $data) {
260
		global $app, $conf;
261

262
263
		//* Load libraries
		$app->uses("getconf,tpl");
264

265
266
		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
267

Florian Schaal's avatar
Florian Schaal committed
268
269
		//* Get the bind version
		$bind_caa = false;
270
        $bind = explode("\n", shell_exec('which named bind 2> /dev/null'));
Florian Schaal's avatar
Florian Schaal committed
271
272
273
274
275
276
277
278
        $bind = reset($bind);
        if(is_executable($bind)) {
			exec($bind . ' -v 2>&1', $tmp);
			$bind_caa = @(version_compare($tmp[0],"BIND 9.9.6", '>='))?true:false;
			unset($tmp);
		}
		unset($bind);

279
		//* Write the domain file
tbrehm's avatar
tbrehm committed
280
		if(!empty($data['new']['id'])) {
tbrehm's avatar
tbrehm committed
281
282
			$tpl = new tpl();
			$tpl->newTemplate("bind_pri.domain.master");
283

tbrehm's avatar
tbrehm committed
284
285
			$zone = $data['new'];
			$tpl->setVar($zone);
286

A. Täffner's avatar
A. Täffner committed
287
			$records = $app->db->queryAllRecords("SELECT * FROM dns_rr WHERE zone = ? AND active = 'Y'", $zone['id']);
288
			if(is_array($records) && !empty($records)){
289
				$caa_add_rec = -1;
290
291
				for($i=0;$i<sizeof($records);$i++){
					if($records[$i]['ttl'] == 0) $records[$i]['ttl'] = '';
292
					if($records[$i]['name'] == '') $records[$i]['name'] = '@';
293
294
295
296
					//* Split TXT records, if nescessary
					if($records[$i]['type'] == 'TXT' && strlen($records[$i]['data']) > 255) {
						$records[$i]['data'] = implode('" "',str_split( $records[$i]['data'], 255));
					}
Florian Schaal's avatar
Florian Schaal committed
297
298
299
300
301
302
303
304
					//* CAA-Records - Type257 for older bind-versions
					if($records[$i]['type'] == 'CAA' && !$bind_caa) {
						$records[$i]['type'] = 'TYPE257';
						$temp = explode(' ', $records[$i]['data']);
						unset($temp[0]);
						$records[$i]['data'] = implode(' ', $temp);
						$data_new = str_replace(array('"', ' '), '', $records[$i]['data']);
						$hex = unpack('H*', $data_new);
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
						if ($temp[1] == 'issuewild') {
							$hex[1] = '0009'.strtoupper($hex[1]);
							if ($caa_add_rec == -1) {
								// add issue ";" if only issuewild recordsa
								$caa_add_rec = array_push($records, $records[$i]);
								$records[$caa_add_rec-1]['data'] = "\# 8 000569737375653B";
							}
						} else {
							$hex[1] = '0005'.strtoupper($hex[1]);
							if ($caa_add_rec > 0) {
								// remove previously added issue ";" 
								array_pop($records);
							}
							$caa_add_rec = -2;
						}
Florian Schaal's avatar
Florian Schaal committed
320
321
322
323
						$length = strlen($hex[1])/2;
						$data_new = "\# $length $hex[1]";
						$records[$i]['data'] = $data_new;
					}
324
325
				}
			}
326
327
			$tpl->setLoop('zones', $records);

Helmo's avatar
Helmo committed
328
			$filename = $dns_config['bind_zonefiles_dir'].'/' . $this->zone_file_prefix() . str_replace("/", "_", substr($zone['origin'], 0, -1));
329

330
			$old_zonefile = @file_get_contents($filename);
Helmo's avatar
Helmo committed
331
332
333
			$rendered_zone = $tpl->grab();
			file_put_contents($filename, $rendered_zone);

334
335
			chown($filename, $dns_config['bind_user']);
			chgrp($filename, $dns_config['bind_group']);
336

Helmo's avatar
Helmo committed
337
			// Store also in the db for exports.
Helmo's avatar
Helmo committed
338
			$app->dbmaster->query("UPDATE `dns_soa` SET `rendered_zone`=? WHERE id=?", $rendered_zone, $zone['id']);
Helmo's avatar
Helmo committed
339

340
341
			//* Check the zonefile
			if(is_file($filename.'.err')) unlink($filename.'.err');
342
343
344
			$app->system->exec_safe('named-checkzone ? ?', $zone['origin'], $filename);
			$out = $app->system->last_exec_out();
			$return_status = $app->system->last_exec_retcode();
345
			if($return_status === 0) {
346
				$app->log("Writing BIND domain file: ".$filename, LOGLEVEL_DEBUG);
347
			} else {
Jesse Norell's avatar
Jesse Norell committed
348
				$loglevel = @($dns_config['disable_bind_log'] === 'y') ? LOGLEVEL_DEBUG : LOGLEVEL_WARN;
349
350
351
352
353
354
355
356
				$app->log("Writing BIND domain file failed: ".$filename." ".implode(' ', $out), $loglevel);
				if(is_array($out) && !empty($out)){
					$app->log('Reason for Bind restart failure: '.implode("\n", $out), $loglevel);
					$app->dbmaster->datalogError(implode("\n", $out));
				}
				if ($old_zonefile != '') {
					rename($filename, $filename.'.err');
					file_put_contents($filename, $old_zonefile);
357
358
					chown($filename, $dns_config['bind_user']);
					chgrp($filename, $dns_config['bind_group']);
359
				} else {
360
					rename($filename, $filename.'.err');
361
				}
362
			}
tbrehm's avatar
tbrehm committed
363
364
			unset($tpl);
			unset($records);
365
			unset($records_out);
tbrehm's avatar
tbrehm committed
366
367
			unset($zone);
		}
368

369
		//* DNSSEC-Implementation
370
		if($data['old']['origin'] != $data['new']['origin']) {
371
			if (@$data['old']['dnssec_initialized'] == 'Y' && strlen(@$data['old']['origin']) > 3) $this->soa_dnssec_delete($data); //delete old keys
Till Brehm's avatar
Till Brehm committed
372
373
			if ($data['new']['dnssec_wanted'] == 'Y') $this->soa_dnssec_create($data);
		} elseif($data['old']['dnssec_algo'] != $data['new']['dnssec_algo']) {
374
			$app->log("DNSSEC Algorithm has changed: ".$data['new']['dnssec_algo'], LOGLEVEL_DEBUG);
Till Brehm's avatar
Till Brehm committed
375
			if ($data['new']['dnssec_wanted'] == 'Y') $this->soa_dnssec_create($data);
376
377
378
		} elseif ($data['new']['dnssec_wanted'] == 'Y' && $data['old']['dnssec_initialized'] == 'N') {
			$this->soa_dnssec_create($data);
		} elseif ($data['new']['dnssec_wanted'] == 'N' && $data['old']['dnssec_initialized'] == 'Y') {	//delete old signed file if dnssec is no longer wanted
Helmo's avatar
Helmo committed
379
			$filename = $dns_config['bind_zonefiles_dir'].'/' . $this->zone_file_prefix() . str_replace("/", "_", substr($data['old']['origin'], 0, -1));
380
			if(is_file($filename.'.signed')) unlink($filename.'.signed');
381
382
383
 		} elseif ($data['new']['dnssec_wanted'] == 'Y') {
			$this->soa_dnssec_update($data);
		}
384
		// END DNSSEC
385

386
		//* rebuild the named.conf file if the origin has changed or when the origin is inserted.
387
		//if($this->action == 'insert' || $data['old']['origin'] != $data['new']['origin']) {
388
		$this->write_named_conf($data, $dns_config);
389
		//}
390

391
392
		//* Delete old domain file, if domain name has been changed
		if($data['old']['origin'] != $data['new']['origin']) {
Helmo's avatar
Helmo committed
393
			$filename = $dns_config['bind_zonefiles_dir'].'/' . $this->zone_file_prefix() . str_replace("/", "_", substr($data['old']['origin'], 0, -1));
394

395
396
			if(is_file($filename)) unlink($filename);
			if(is_file($filename.'.err')) unlink($filename.'.err');
397
			if(is_file($filename.'.signed')) unlink($filename.'.signed');
398
 		}
399

400
401
402
403
404
405
		//* Restart bind nameserver if update_acl is not empty, otherwise reload it
		if($data['new']['update_acl'] != '') {
			$app->services->restartServiceDelayed('bind', 'restart');
		} else {
			$app->services->restartServiceDelayed('bind', 'reload');
		}
406

407
	}
408
409

	function soa_delete($event_name, $data) {
410
		global $app, $conf;
411

412
		//* load the server configuration options
413
		$app->uses("getconf,tpl");
414
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
415

416
		//* rebuild the named.conf file
417
418
		$this->write_named_conf($data, $dns_config);

419
		//* Delete the domain file
Helmo's avatar
Helmo committed
420
		$zone_file_name = $dns_config['bind_zonefiles_dir'].'/' . $this->zone_file_prefix() . str_replace("/", "_", substr($data['old']['origin'], 0, -1));
421
		if(is_file($zone_file_name)) unlink($zone_file_name);
422
		if(is_file($zone_file_name.'.err')) unlink($zone_file_name.'.err');
423
424
		$app->log("Deleting BIND domain file: ".$zone_file_name, LOGLEVEL_DEBUG);

425
		//* Reload bind nameserver
426
427
		$app->services->restartServiceDelayed('bind', 'reload');

428
429
	}

430
	function slave_insert($event_name, $data) {
431
		global $app, $conf;
432

433
		$this->action = 'insert';
434
435
		$this->slave_update($event_name, $data);

436
	}
437
438

	function slave_update($event_name, $data) {
439
		global $app, $conf;
440

441
442
		//* Load libraries
		$app->uses("getconf,tpl");
443

444
445
		//* load the server configuration options
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
446

447
448
		//* rebuild the named.conf file if the origin has changed or when the origin is inserted.
		//if($this->action == 'insert' || $data['old']['origin'] != $data['new']['origin']) {
449
		$this->write_named_conf($data, $dns_config);
450
		//}
451

452
453
		//* Delete old domain file, if domain name has been changed
		if($data['old']['origin'] != $data['new']['origin']) {
Helmo's avatar
Helmo committed
454
			$filename = $dns_config['bind_zonefiles_dir'].'/' . $this->zone_file_prefix() . str_replace("/", "_", substr($data['old']['origin'], 0, -1));
455
456
			if(is_file($filename)) unset($filename);
		}
457

458
		//* Ensure that the named slave directory is writable by the named user
Helmo's avatar
Helmo committed
459
		$slave_record_dir = $dns_config['bind_zonefiles_dir'].'/'.$this->slave_zone_file_prefix();
460
		if(!@is_dir($slave_record_dir)) mkdir($slave_record_dir, 0770, true);
461
462
463
		chown($slave_record_dir, $dns_config['bind_user']);
		chgrp($slave_record_dir, $dns_config['bind_group']);

464
		//* Reload bind nameserver
465
466
		$app->services->restartServiceDelayed('bind', 'reload');

467
	}
468
469

	function slave_delete($event_name, $data) {
470
		global $app, $conf;
471
472


473
474
475
		//* load the server configuration options
		$app->uses("getconf,tpl");
		$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns');
476

477
		//* rebuild the named.conf file
478
479
		$this->write_named_conf($data, $dns_config);

480
		//* Delete the domain file
Helmo's avatar
Helmo committed
481
		$zone_file_name = $dns_config['bind_zonefiles_dir'].'/' . $this->slave_zone_file_prefix() . str_replace("/", "_", substr($data['old']['origin'], 0, -1));
482
		if(is_file($zone_file_name)) unlink($zone_file_name);
483
484
		$app->log("Deleting BIND domain file for secondary zone: ".$zone_file_name, LOGLEVEL_DEBUG);

485
		//* Reload bind nameserver
486
487
488
		$app->services->restartServiceDelayed('bind', 'reload');


489
	}
490
491

	function rr_insert($event_name, $data) {
492
		global $app, $conf;
493

494
		//* Get the data of the soa and call soa_update
495
		$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $data['new']['zone']);
496
497
498
		$data["new"] = $tmp;
		$data["old"] = $tmp;
		$this->action = 'update';
499
		$this->soa_update($event_name, $data);
500
501

	}
502
503

	function rr_update($event_name, $data) {
504
		global $app, $conf;
505

506
		//* Get the data of the soa and call soa_update
507
		$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $data['new']['zone']);
508
509
510
		$data["new"] = $tmp;
		$data["old"] = $tmp;
		$this->action = 'update';
511
512
		$this->soa_update($event_name, $data);

513
	}
514
515

	function rr_delete($event_name, $data) {
516
		global $app, $conf;
517

518
		//* Get the data of the soa and call soa_update
519
		$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ?", $data['old']['zone']);
520
521
522
		$data["new"] = $tmp;
		$data["old"] = $tmp;
		$this->action = 'update';
523
524
		$this->soa_update($event_name, $data);

525
	}
526
527
528

	//##################################################################

529
530
	function write_named_conf($data, $dns_config) {
		global $app, $conf;
531
532

		//* Only write the master file for the current server
533
		$tmps = $app->db->queryAllRecords("SELECT origin, xfer, also_notify, update_acl, dnssec_wanted FROM dns_soa WHERE active = 'Y' AND server_id=?", $conf["server_id"]);
534
		$zones = array();
535

536
		//* Check if the current zone that triggered this function has at least one NS record
537

Helmo's avatar
Helmo committed
538
539
		$pri_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/'.$this->zone_file_prefix();
		$sec_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/'.$this->slave_zone_file_prefix();
540

541
		//* Loop trough zones
542
		foreach($tmps as $tmp) {
543
			$zone_file = $pri_zonefiles_path.str_replace("/", "_", substr($tmp['origin'], 0, -1));
544
			if ($tmp['dnssec_wanted'] == 'Y') $zone_file .= '.signed'; //.signed is for DNSSEC-Implementation
545

546
			$options = '';
547
			if(trim($tmp['xfer']) != '') {
548
				$options .= "        allow-transfer {".str_replace(',', ';', $tmp['xfer']).";};\n";
549
			}
550
551
552
			if(trim($tmp['also_notify']) != '') $options .= '        also-notify {'.str_replace(',', ';', $tmp['also_notify']).";};\n";
			if(trim($tmp['update_acl']) != '') $options .= "        allow-update {".str_replace(',', ';', $tmp['update_acl']).";};\n";

553
			if(file_exists($zone_file)) {
554
555
556
557
				$zones[] = array( 'zone' => substr($tmp['origin'], 0, -1),
					'zonefile_path' => $zone_file,
					'options' => $options
				);
558
			}
559
		}
560

561
562
		$tpl = new tpl();
		$tpl->newTemplate("bind_named.conf.local.master");
563
564
		$tpl->setLoop('zones', $zones);

565
		//* And loop through the secondary zones, but only for the current server
566
		$tmps_sec = $app->db->queryAllRecords("SELECT origin, xfer, ns FROM dns_slave WHERE active = 'Y' AND server_id=?", $conf["server_id"]);
567
568
569
		$zones_sec = array();

		foreach($tmps_sec as $tmp) {
570

571
			// When you have more than one master, the serial number is used to determine which Master has the most current version of the zone by the
572
			// slaves.  The slaves actually ask for the SOA record from each Master when refreshing.
573
			$options = "        masters {".str_replace(',', ';', $tmp['ns']).";};\n";
574
575
576
577
578
579
580
581
582
583
584
			if(trim($tmp['xfer']) != '') {
				$options .= "        allow-transfer {".str_replace(',', ';', $tmp['xfer']).";};\n";
			} else {
				$options .= "        allow-transfer {none;};\n";
			}


			$zones_sec[] = array( 'zone' => substr($tmp['origin'], 0, -1),
				'zonefile_path' => $sec_zonefiles_path.str_replace("/", "_", substr($tmp['origin'], 0, -1)),
				'options' => $options
			);
585
		}
586

587
588
		$tpl_sec = new tpl();
		$tpl_sec->newTemplate("bind_named.conf.local.slave");
589
590
591
592
593
594
595
596
		$tpl_sec->setLoop('zones', $zones_sec);

		file_put_contents($dns_config['named_conf_local_path'], $tpl->grab()."\n".$tpl_sec->grab());
		$app->log("Writing BIND named.conf.local file: ".$dns_config['named_conf_local_path'], LOGLEVEL_DEBUG);

		unset($tpl_sec);
		unset($zones_sec);
		unset($tmps_sec);
597
		unset($tpl);
598
599
		unset($zones);
		unset($tmps);
600

601
	}
602
603


Helmo's avatar
Helmo committed
604
605
606
607
608
609
610
611
612
613
	function zone_file_prefix() {
		//TODO : change this when distribution information has been integrated into server record
		return	(file_exists('/etc/gentoo-release')) ? 'pri/' : 'pri.';
	}
	function slave_zone_file_prefix() {
		//TODO : change this when distribution information has been integrated into server record
		return	(file_exists('/etc/gentoo-release')) ? 'sec/' : 'slave/sec.';
	}


614

615
616
617

} // end class

618
?>