\n";
43 | exit(1);
44 | }
45 | $comment = $argv[1];
46 |
47 | $zones = $zone_dir->list_zones();
48 | $zone_exports = array();
49 | foreach($zones as $zone) {
50 | $zone_exports[$zone->name] = $zone->export_as_bind9_format();
51 | }
52 | $original_dir = getcwd();
53 | if(chdir($config['git_tracked_export']['path'])) {
54 | foreach($zone_exports as $name => $export) {
55 | $name = DNSZoneName::unqualify($name);
56 | $fh = fopen($name, 'w');
57 | fwrite($fh, $export);
58 | fclose($fh);
59 | }
60 | exec('LANG=en_US.UTF-8 git add -A');
61 | exec('LANG=en_US.UTF-8 git commit --author '.escapeshellarg($active_user->name.' <'.$active_user->email.'>').' -m '.escapeshellarg($comment));
62 | chdir($original_dir);
63 | }
64 |
--------------------------------------------------------------------------------
/scripts/ldap_update.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | list_users();
23 |
24 | foreach($users as $user) {
25 | if($user->auth_realm == 'LDAP') {
26 | try {
27 | $user->get_details_from_ldap();
28 | $user->update();
29 | } catch(UserNotFoundException $e) {
30 | $user->active = 0;
31 | $user->update();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/templates/base.php:
--------------------------------------------------------------------------------
1 | get('web_config');
18 | header('X-Frame-Options: DENY');
19 | header("Content-Security-Policy: default-src 'self'");
20 | ?>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | get('title'))?>
32 | get('head'), ESC_NONE) ?>
33 |
34 |
Skip to main content
35 |
36 |
37 |
54 |
55 |
56 | get('menu_items') as $name => $contents) { ?>
57 |
58 |
59 |
60 |
65 |
66 |
67 | get('relative_request_url')) out(' class="active"', ESC_NONE); ?>>
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | get('alerts') as $alert) { ?>
76 |
77 | ×
78 | content, $alert->escaping)?>
79 |
80 |
81 | get('content'), ESC_NONE) ?>
82 |
83 |
84 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/templates/csrf.php:
--------------------------------------------------------------------------------
1 |
18 | Form submission failed
19 | Your request was missing the required security token. Please try submitting your request again.
20 |
--------------------------------------------------------------------------------
/templates/error403.php:
--------------------------------------------------------------------------------
1 |
18 | Access denied
19 | Sorry, but you don't have permission to view this page.
20 |
--------------------------------------------------------------------------------
/templates/error404.php:
--------------------------------------------------------------------------------
1 |
18 | Page not found
19 | Sorry, but the address you've given doesn't seem to point to a valid page.
20 | If you got here by following a link, please report it to us . Otherwise, please make sure that you have typed the address correctly, or just start browsing from the home page .
21 |
--------------------------------------------------------------------------------
/templates/error500.php:
--------------------------------------------------------------------------------
1 |
18 | get('error_details')) { ?>
19 | Error
20 | get('exception_class')) ?> "get('error_details')->getMessage()) ?> "
21 | Occurred in get('error_details')->getFile().' line '.$this->get('error_details')->getLine()) ?>
22 | Stack trace
23 |
24 |
25 |
26 | Function
27 | Arguments
28 | Location
29 |
30 |
31 |
32 | get('error_details')->getTrace() as $stack_line) { ?>
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Oops! Something went wrong!
53 | Sorry, but it looks like something needs fixing on the system. The problem has been automatically reported to the administrators, but if you wish, you can also provide additional information about what you were doing that may have triggered the error.
54 |
55 |
--------------------------------------------------------------------------------
/templates/error503.php:
--------------------------------------------------------------------------------
1 |
18 | System is down for maintenance
19 | Sorry for the inconvenience. We should be back soon though, so press the reload button in your browser in a few minutes to try again.
20 |
--------------------------------------------------------------------------------
/templates/error503_upstream.php:
--------------------------------------------------------------------------------
1 | get('active_user');
18 | ?>
19 | DNS server communication failure
20 | The DNS server is not responding.
21 | admin) { ?>
22 | Make sure that the following PowerDNS parameters are set correctly in pdns.conf
:
23 | webserver=yes
24 | webserver-address=...
25 | webserver-allow-from=...
26 | webserver-port=...
27 | api=yes
28 | api-key=...
29 | Reload PowerDNS after making changes to this file.
30 | Also check the values set in the [powerdns]
section of the DNS UI configuration file (config/config.ini
).
31 |
32 |
--------------------------------------------------------------------------------
/templates/functions.php:
--------------------------------------------------------------------------------
1 | '.hesc($before).' '.hesc($after).' ', ESC_NONE);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/templates/settings.php:
--------------------------------------------------------------------------------
1 | get('replication_types');
18 | $ns_templates = $this->get('ns_templates');
19 | $soa_templates = $this->get('soa_templates');
20 | ?>
21 | Settings
22 |
--------------------------------------------------------------------------------
/templates/template.php:
--------------------------------------------------------------------------------
1 | get('type');
18 | $template = $this->get('template');
19 | ?>
20 |
21 |
86 |
--------------------------------------------------------------------------------
/templates/templates.php:
--------------------------------------------------------------------------------
1 | get('soa_templates');
18 | $templates['ns'] = $this->get('ns_templates');
19 | $type = $this->get('type');
20 | $types = array('soa', 'ns');
21 | ?>
22 | get('title'))?>
23 | These templates are used when creating new zones to pre-populate the form fields. Any number of preset templates can be defined below. If a default is selected, its values will be pre-filled without user interaction.
24 |
25 |
29 |
30 |
31 |
32 |
Template list
33 |
34 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
35 |
36 |
37 |
38 |
39 | Type
40 |
41 | Template name
42 | Actions
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
55 | name)?>
56 |
57 | Edit
58 | Delete
59 | default) { ?>
60 | Default
61 |
62 | Set as default
63 |
64 |
65 |
66 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
Create template
78 |
79 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
80 |
86 |
87 |
93 |
99 |
105 |
111 |
117 |
123 |
129 |
130 |
136 |
137 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/templates/user.php:
--------------------------------------------------------------------------------
1 | get('active_user');
18 | $user = $this->get('user');
19 | $changesets = $this->get('changesets');
20 | global $output_formatter;
21 | ?>
22 | name)?> (uid)?>)
23 | User details
24 | admin && $user->auth_realm === 'local') { ?>
25 |
26 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
27 |
33 |
39 |
45 |
53 |
61 |
66 |
67 |
68 |
69 | User ID
70 | uid)?>
71 | Full name
72 | name)?>
73 | Email address
74 | email)?>
75 |
76 |
77 | Activity
78 |
79 | No activity yet.
80 |
81 |
82 |
83 |
84 | Date / time
85 | Comment
86 | Requester
87 | Zone
88 | Changes
89 |
90 |
91 |
92 |
93 |
94 |
95 | change_date->format('Y-m-d H:i:s'))?>
96 | changeset_comment_format($changeset->comment), ESC_NONE) ?>
97 | requester) { ?>requester->name)?>
98 | zone->name)))?>
99 | deleted.'/+'.$changeset->added)?>
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/templates/user_not_found.php:
--------------------------------------------------------------------------------
1 |
18 | User not found
19 | The specified user get('uid'))?> could not be found.
20 |
--------------------------------------------------------------------------------
/templates/users.php:
--------------------------------------------------------------------------------
1 | get('users');
18 | ?>
19 | Users
20 |
24 |
25 |
26 |
User list
27 |
28 |
29 |
30 | User ID
31 | Full name
32 | Email address
33 | Directory
34 | Active
35 | Admin
36 |
37 |
38 |
39 |
40 | active) out(' class="text-muted"', ESC_NONE) ?>>
41 | uid)?>
42 | name)?>
43 | email)?>
44 | auth_realm ?? ''))?>
45 | active ? '✓' : '')?>
46 | admin ? '✓' : '')?>
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Create user
54 |
You can create users in the local directory here. It is not possible to create users in your LDAP directory from the DNS UI.
55 |
56 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
57 |
63 |
69 |
75 |
83 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/templates/zone_add_failed.php:
--------------------------------------------------------------------------------
1 |
18 | Zone creation failed
19 | The zone creation failed. The following error message was given: get('message'))?>
20 |
--------------------------------------------------------------------------------
/templates/zone_update_failed.php:
--------------------------------------------------------------------------------
1 |
18 | Zone update failed
19 | The zone update failed. The following error message was given: get('message'))?>
20 |
--------------------------------------------------------------------------------
/templates/zonedeleted.php:
--------------------------------------------------------------------------------
1 | get('active_user');
18 | $zone = $this->get('zone');
19 | $deletion = $this->get('deletion');
20 | ?>
21 | Zone name)))?> does not exist
22 | This zone no longer exists on the DNS server.
23 |
24 |
25 | Deletion requested by
26 | name)?> on format('Y-m-d H:i:s'))?>
27 | Deletion confirmed by
28 | name)?> on format('Y-m-d H:i:s'))?>
29 |
30 | Zone archive
31 | This is a snapshot of the zone's contents prior to its deletion.
32 |
33 |
34 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
35 | Confirm zone restore
36 | Restore zone from archive…
37 |
38 |
39 |
--------------------------------------------------------------------------------
/templates/zoneexport.php:
--------------------------------------------------------------------------------
1 | get('zone');
18 | $rrsets = $this->get('rrsets');
19 | echo $zone->export_as_bind9_format();
20 |
--------------------------------------------------------------------------------
/templates/zoneimport.php:
--------------------------------------------------------------------------------
1 | get('zone');
18 | $modifications = $this->get('modifications');
19 | $checked = 'checked ';
20 | $count = 0;
21 | $limit = 2500;
22 | ?>
23 | Import preview for name)))?> zone update
24 |
25 | No changes have been made! Go back .
26 |
27 |
28 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
29 | 0) { ?>
30 | New resource recordsets
31 |
71 |
72 | 0) { ?>
73 | Updated resource recordsets
74 |
106 |
107 | 0) { ?>
108 | Deleted resource recordsets
109 |
149 |
150 |
151 |
152 | By default only the first changes (out of ) have been selected for import as larger imports may be rejected by PowerDNS.
153 | It is recommended that you run this import multiple times until all changes have been imported.
154 |
155 |
156 | Update comment
157 |
158 | Confirm selected changes
159 | Cancel import
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/templates/zonesplit.php:
--------------------------------------------------------------------------------
1 | get('zone');
19 | $newzonename = $this->get('newzonename');
20 | $suffix = $this->get('suffix');
21 | $split = $this->get('split');
22 | $cname_error = $this->get('cname_error');
23 | ?>
24 | Zone split of from name)))?>
25 |
26 | get('active_user')->get_csrf_field(), ESC_NONE) ?>
27 |
28 | No records match this pattern.
29 | Go back
30 |
31 |
85 |
86 |
87 | Change comment
88 |
89 |
90 |
91 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/templates/zonesplitcompleted.php:
--------------------------------------------------------------------------------
1 | get('zone');
19 | $newzonename = $this->get('newzonename');
20 | ?>
21 | Zone split of from name)))?>
22 |
26 |
--------------------------------------------------------------------------------
/tests/DNSContentTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('ns1.example.com. hostmaster.example.com. 2017080806 10800 3600 1209600 3600', DNSContent::encode('ns1.example.com. hostmaster.example.com. 2017080806 3H 1H 2W 1H', 'SOA', 'example.com.'));
14 | }
15 | public function testEncodeTxt() {
16 | // TXT should be escaped and quoted
17 | $this->assertEquals('"hello \"world\" \\\\"', DNSContent::encode('hello "world" \\', 'TXT', 'example.com.'));
18 | }
19 | public function testEncodeMx() {
20 | // MX hostname should be canonified
21 | $this->assertEquals('10 example.com.', DNSContent::encode('10 @', 'MX', 'example.com.'));
22 | $this->assertEquals('10 test.example.com.', DNSContent::encode('10 test', 'MX', 'example.com.'));
23 | $this->assertEquals('10 test.example.org.', DNSContent::encode('10 test.example.org.', 'MX', 'example.com.'));
24 | }
25 | public function testEncodeSrv() {
26 | // SRV hostname should be canonified
27 | $this->assertEquals('0 5 5060 example.com.', DNSContent::encode('0 5 5060 @', 'SRV', 'example.com.'));
28 | $this->assertEquals('0 5 5060 sipserver.example.com.', DNSContent::encode('0 5 5060 sipserver', 'SRV', 'example.com.'));
29 | $this->assertEquals('0 5 5060 sipserver.example.org.', DNSContent::encode('0 5 5060 sipserver.example.org.', 'SRV', 'example.com.'));
30 | }
31 | public function testEncodeCname() {
32 | // CNAME should be canonified
33 | $this->assertEquals('example.com.', DNSContent::encode('@', 'CNAME', 'example.com.'));
34 | $this->assertEquals('test.example.com.', DNSContent::encode('test', 'CNAME', 'example.com.'));
35 | $this->assertEquals('test.example.org.', DNSContent::encode('test.example.org.', 'CNAME', 'example.com.'));
36 | }
37 | public function testEncodeA() {
38 | // A records should be untouched
39 | $this->assertEquals('192.0.2.1', DNSContent::encode('192.0.2.1', 'A', 'example.com.'));
40 | }
41 |
42 | public function testDecodeTxt() {
43 | // TXT should be unquoted and unescaped
44 | $this->assertEquals('hello "world" \\', DNSContent::decode('"hello \"world\" \\\\"', 'TXT', 'example.com.'));
45 | }
46 | public function testDecodeMx() {
47 | // MX hostname should be abbreviated
48 | $this->assertEquals('10 @', DNSContent::decode('10 example.com.', 'MX', 'example.com.'));
49 | $this->assertEquals('10 test', DNSContent::decode('10 test.example.com.', 'MX', 'example.com.'));
50 | $this->assertEquals('10 test.example.org.', DNSContent::decode('10 test.example.org.', 'MX', 'example.com.'));
51 | }
52 | public function testDecodeSrv() {
53 | // SRV hostname should be abbreviated
54 | $this->assertEquals('0 5 5060 @', DNSContent::decode('0 5 5060 example.com.', 'SRV', 'example.com.'));
55 | $this->assertEquals('0 5 5060 sipserver', DNSContent::decode('0 5 5060 sipserver.example.com.', 'SRV', 'example.com.'));
56 | $this->assertEquals('0 5 5060 sipserver.example.org.', DNSContent::decode('0 5 5060 sipserver.example.org.', 'SRV', 'example.com.'));
57 | }
58 | public function testDecodeCname() {
59 | // CNAME should be abbreviated
60 | $this->assertEquals('@', DNSContent::decode('example.com.', 'CNAME', 'example.com.'));
61 | $this->assertEquals('test', DNSContent::decode('test.example.com.', 'CNAME', 'example.com.'));
62 | $this->assertEquals('test.example.org.', DNSContent::decode('test.example.org.', 'CNAME', 'example.com.'));
63 | }
64 | public function testDecodeA() {
65 | // A records should be untouched
66 | $this->assertEquals('192.0.2.1', DNSContent::decode('192.0.2.1', 'A', 'example.com.'));
67 | }
68 |
69 | public function testBind9FormatSoa() {
70 | // SOA record should be formatted nicely
71 | $nice_format = "ns1 hostmaster.example.com. (\n";
72 | $nice_format .= " 2017080806 ; serial\n";
73 | $nice_format .= " 3H ; refresh\n";
74 | $nice_format .= " 1H ; retry\n";
75 | $nice_format .= " 2W ; expire\n";
76 | $nice_format .= " 1H ; default ttl\n";
77 | $nice_format .= " )\n";
78 | $this->assertEquals($nice_format, DNSContent::bind9_format('ns1.example.com. hostmaster.example.com. 2017080806 10800 3600 1209600 3600', 'SOA', 'example.com.'));
79 | }
80 | public function testBind9FormatTxt() {
81 | // TXT record encoding should be untouched (should already be encoded)
82 | $this->assertEquals('"hello \"world\" \\\\"', DNSContent::bind9_format('"hello \"world\" \\\\"', 'TXT', 'example.com.'));
83 | // TXT records should be split at 255 bytes
84 | $this->assertEquals(
85 | '"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'.
86 | '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'.
87 | '0123456789012345678901234567890123456789012345678901234" "567890123456789012345678901234567890123456789"',
88 | DNSContent::bind9_format('"'.str_repeat("0123456789", 30).'"', 'TXT', 'example.com.')
89 | );
90 | $this->assertEquals(
91 | '"\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789'.
92 | '\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789'.
93 | '\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\123456789\\\\1234" "56789\\\\123456789\\\\123456789\\\\123456789\\\\123456789"',
94 | DNSContent::bind9_format('"'.str_repeat("\\123456789", 30).'"', 'TXT', 'example.com.')
95 | );
96 | }
97 | public function testBind9FormatA() {
98 | // A records should be untouched
99 | $this->assertEquals('192.0.2.1', DNSContent::bind9_format('192.0.2.1', 'A', 'example.com.'));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/DNSKEYTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('50036', DNSKEY::get_tag(257, 3, 13, '+lIB+O45g/Uea2u5v8mhWaW9pi4CaKEKiPK3AbYH5Uja9GW7+m/vUOBCHwICf3hLtZ5PXgorjP/td9qutBneFw=='));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/DNSNameTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('foobar', DNSName::abbreviate('foobar.example.com.', 'example.com.'));
14 | // Non-matching domain
15 | $this->assertEquals('foobar.example.com.', DNSName::abbreviate('foobar.example.com.', 'example.org.'));
16 | // Non-matching domain (partial match)
17 | $this->assertEquals('foobar.example.com.', DNSName::abbreviate('foobar.example.com.', 'test.com.'));
18 | // Non-matching domain (substring match)
19 | $this->assertEquals('foobar.example.com.', DNSName::abbreviate('foobar.example.com.', 'bar.example.com.'));
20 | // Domain root
21 | $this->assertEquals('@', DNSName::abbreviate('foobar.example.com.', 'foobar.example.com.'));
22 | }
23 |
24 | public function testCanonify() {
25 | // Normal record
26 | $this->assertEquals('foobar.example.com.', DNSName::canonify('foobar', 'example.com.'));
27 | // Dot-qualified FQDN
28 | $this->assertEquals('foobar.example.org.', DNSName::canonify('foobar.example.org.', 'example.com.'));
29 | // Not dot-qualified FQDN
30 | $this->assertEquals('foobar.example.org.example.com.', DNSName::canonify('foobar.example.org', 'example.com.'));
31 | // Domain root record
32 | $this->assertEquals('foobar.example.com.', DNSName::canonify('@', 'foobar.example.com.'));
33 | // . origin
34 | $this->assertEquals('foobar.example.com.', DNSName::canonify('foobar.example.com', '.'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/DNSTimeTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('0', DNSTime::abbreviate(0));
18 | // Seconds
19 | $this->assertEquals('1', DNSTime::abbreviate(1));
20 | $this->assertEquals('59', DNSTime::abbreviate(59));
21 | // Minutes
22 | $this->assertEquals('1M', DNSTime::abbreviate(1 * $minute));
23 | $this->assertEquals('59M', DNSTime::abbreviate(59 * $minute));
24 | // Combined minutes + seconds
25 | $this->assertEquals('61', DNSTime::abbreviate(1 * $minute + 1));
26 | // Hours
27 | $this->assertEquals('1H', DNSTime::abbreviate(1 * $hour));
28 | $this->assertEquals('23H', DNSTime::abbreviate(23 * $hour));
29 | // Combined hours + minutes
30 | $this->assertEquals('61M', DNSTime::abbreviate(1 * $hour + 1 * $minute));
31 | // Combined hours + minutes + seconds
32 | $this->assertEquals('3661', DNSTime::abbreviate(1 * $hour + 1 * $minute + 1));
33 | // Days
34 | $this->assertEquals('1D', DNSTime::abbreviate(1 * $day));
35 | $this->assertEquals('6D', DNSTime::abbreviate(6 * $day));
36 | // Weeks
37 | $this->assertEquals('1W', DNSTime::abbreviate(1 * $week));
38 | $this->assertEquals('99W', DNSTime::abbreviate(99 * $week));
39 | }
40 |
41 | public function testExpand() {
42 | $minute = 60;
43 | $hour = 60 * $minute;
44 | $day = 24 * $hour;
45 | $week = 7 * $day;
46 | // Zero
47 | $this->assertEquals(0, DNSTime::expand('0'));
48 | // Seconds
49 | $this->assertEquals(1, DNSTime::expand('1'));
50 | $this->assertEquals(59, DNSTime::expand('59'));
51 | // Minutes
52 | $this->assertEquals(1 * $minute, DNSTime::expand('1M'));
53 | $this->assertEquals(59 * $minute, DNSTime::expand('59M'));
54 | // Combined minutes + seconds
55 | $this->assertEquals(1 * $minute + 1, DNSTime::expand('1M1S'));
56 | // Hours
57 | $this->assertEquals(1 * $hour, DNSTime::expand('1H'));
58 | $this->assertEquals(23 * $hour, DNSTime::expand('23H'));
59 | // Combined hours + minutes
60 | $this->assertEquals(1 * $hour + 1 * $minute, DNSTime::expand('1H1M'));
61 | // Combined hours + minutes + seconds
62 | $this->assertEquals(1 * $hour + 1 * $minute + 1, DNSTime::expand('1H1M1S'));
63 | // Days
64 | $this->assertEquals(1 * $day, DNSTime::expand('1D'));
65 | $this->assertEquals(6 * $day, DNSTime::expand('6D'));
66 | // Weeks
67 | $this->assertEquals(1 * $week, DNSTime::expand('1W'));
68 | $this->assertEquals(99 * $week, DNSTime::expand('99W'));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/vagrant/README.md:
--------------------------------------------------------------------------------
1 | Opera DNS UI Vagrant
2 | ====================
3 |
4 | Vagrant and ansible configuration to get a local development or test environment
5 | up and running quickly and efficiently.
6 |
7 | Requirements
8 | ------------
9 | * Vagrant 2+
10 |
11 | Usage
12 | -----
13 | 1. Check out this repository
14 | 2. Enter the 'vagrant' directory
15 | 3. Run `vagrant up`
16 | 4. By default vagrant will map the webserver to port 8000, and the DNS server to
17 | port 5300, but it will pick another port when it is in use. Look for:
18 | ```
19 | ==> default: Forwarding ports...
20 | default: 80 (guest) => 8000 (host) (adapter 1)
21 | default: 53 (guest) => 5300 (host) (adapter 1)
22 | ```
23 | 5. Browse to http://localhost:8000 (or the port found in step 4)
24 | 6. Dig using `dig @127.0.0.1 -p 5300` (or the port found in step 4)
25 |
26 | Updating
27 | --------
28 | If you made changes to the ansible scripts or settings and wish to roll out
29 | again, do the following:
30 | 1. Remove your config/config.ini DNS UI configuration (ansible will recreate it)
31 | 2. Run `vagrant provision`
32 |
33 | If you made a lot of impacting changes which might interfere with the existing
34 | vagrant box, just toss & recreate it:
35 | ```
36 | vagrant destroy -f
37 | vagrant up
38 | ```
39 |
--------------------------------------------------------------------------------
/vagrant/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure("2") do |config|
5 | config.vm.box = "ubuntu/xenial64"
6 |
7 | # Expose TCP/80 and UDP/53 on localhost TCP/8080 and UDP/5353 respectively
8 | # If the ports are in use, Vagrant will pick another and let the user know
9 | config.vm.network "forwarded_port", protocol: "tcp", guest: 80, host: 8000,
10 | host_ip: "127.0.0.1", auto_correct: true
11 | config.vm.network "forwarded_port", protocol: "udp", guest: 53, host: 5300,
12 | host_ip: "127.0.0.1", auto_correct: true
13 |
14 | config.vm.synced_folder "..", "/vagrant", id: "application"
15 |
16 | config.vm.provision "ansible_local" do |ansible|
17 | ansible.compatibility_mode = "2.0"
18 | ansible.provisioning_path = "/vagrant/vagrant/ansible"
19 | ansible.playbook = "dns-ui.yml"
20 | ansible.verbose = false
21 | ansible.install = true
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/vagrant/ansible/config.sample.yml:
--------------------------------------------------------------------------------
1 | ---
2 | ## Rename this file to 'config.yml' to activate, and remove the comment from
3 | ## any setting you wish to change from the default value. Defaults are set in
4 | ## roles/*/defaults/main.yml.
5 |
6 | ## Database to install (currently supported: postgresql)
7 | # database_vendor: postgresql
8 |
9 | ## Database settings for DNS UI database
10 | # database_dnsui_schema: dns-ui
11 | # database_dnsui_username: dns-ui
12 | # database_dnsui_password: dns-ui
13 |
14 | ## Database settings for PowerDNS database
15 | # database_powerdns_schema: powerdns
16 | # database_powerdns_username: powerdns
17 | # database_powerdns_password: powerdns
18 |
19 | ## Power DNS repository information
20 | # powerdns_repo_key: https://repo.powerdns.com/FD380FBB-pub.asc
21 | # powerdns_repo: deb [arch=amd64] http://repo.powerdns.com/ubuntu xenial-auth-41 main
22 |
23 | ## PowerDNS default configuration settings
24 | # powerdns_default_soa_name: ns1.dns-ui.local
25 | # powerdns_default_soa_mail: hostmaster.dns-ui.local
26 | ## The default-ksk-algorithm name depends on the PowerDNS version
27 | ## For Version 4.0 use default-ksk-algorithms
28 | ## For Version 4.1 use default-ksk-algorithm
29 | # powerdns_default_ksk_algorithm_setting: default-ksk-algorithm
30 | # powerdns_default_ksk_algorithm: ecdsa256
31 | # powerdns_api_key: pdns-api-key
32 |
33 | ## LDAP settings
34 | # ldap_domain: dns-ui.local
35 | # ldap_admin_password: dnsui
36 | # ldap_organisation: DNS UI
37 | # dnsui_auth_name: DNS UI
38 |
39 | ## DNS UI users
40 | # dnsui_admin_username: admin
41 | # dnsui_admin_password: admin
42 | # dnsui_user_username: user
43 | # dnsui_user_password: user
44 |
--------------------------------------------------------------------------------
/vagrant/ansible/dns-ui.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: all
3 | become_method: sudo
4 | become: true
5 | gather_facts: false
6 | roles:
7 | - dns-ui
8 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | database_vendor: postgresql
3 | database_dnsui_schema: dns-ui
4 | database_dnsui_username: dns-ui
5 | database_dnsui_password: dns-ui
6 | database_powerdns_schema: powerdns
7 | database_powerdns_username: powerdns
8 | database_powerdns_password: powerdns
9 | powerdns_repo_key: https://repo.powerdns.com/FD380FBB-pub.asc
10 | powerdns_repo: deb [arch=amd64] http://repo.powerdns.com/ubuntu xenial-auth-41 main
11 | powerdns_default_soa_name: ns1.dns-ui.local
12 | powerdns_default_soa_mail: hostmaster.dns-ui.local
13 | powerdns_default_ksk_algorithm_setting: default-ksk-algorithm
14 | powerdns_default_ksk_algorithm: ecdsa256
15 | powerdns_api_key: pdns-api-key
16 | ldap_domain: dns-ui.local
17 | ldap_admin_password: dnsui
18 | ldap_organisation: DNS UI
19 | dnsui_auth_name: DNS UI
20 | dnsui_admin_username: admin
21 | dnsui_admin_password: admin
22 | dnsui_user_username: user
23 | dnsui_user_password: user
24 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/apache2.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: enable apache ldap module
3 | apache2_module:
4 | name: authnz_ldap
5 | state: present
6 | register: apache_module
7 |
8 | - name: disable default apache configuration
9 | file:
10 | path: /etc/apache2/sites-enabled/000-default.conf
11 | state: absent
12 |
13 | - name: create dns-ui apache configuration
14 | template:
15 | src: apache2.conf.j2
16 | dest: /etc/apache2/sites-available/dns-ui.conf
17 | owner: root
18 | group: root
19 | mode: 0644
20 | register: apache_config_uploaded
21 |
22 | - name: enable dns-ui apache configuration
23 | file:
24 | src: /etc/apache2/sites-available/dns-ui.conf
25 | dest: /etc/apache2/sites-enabled/dns-ui.conf
26 | state: link
27 | register: apache_config_linked
28 |
29 | - name: restart apache
30 | service:
31 | name: apache2
32 | state: restarted
33 | when: apache_module.changed or apache_config_uploaded.changed or apache_config_linked.changed
34 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: check existence of config file
3 | stat:
4 | path: /vagrant/vagrant/ansible/config.yml
5 | register: override_config
6 |
7 | - name: load override variables
8 | include_vars: /vagrant/vagrant/ansible/config.yml
9 | when: override_config.stat.exists
10 |
11 | - name: generate ldap domain variable
12 | set_fact:
13 | ldap_domain_dc: "dc={{ ldap_domain | regex_replace('\\.', ',dc=') }}"
14 |
15 | - name: test if database vendor is supported
16 | fail:
17 | msg: "Database vendor '{{ database_vendor }}' is (currently) not supported"
18 | when: "database_vendor not in supported_db_vendors"
19 |
20 | - set_fact:
21 | ansible_database_package: python-psycopg2
22 | database_package: postgresql
23 | powerdns_database_package: pdns-backend-pgsql
24 | powerdns_database_config: pdns.gpgsql.conf
25 | php_database_package: php-pgsql
26 | when: "database_vendor == 'postgresql'"
27 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/dns-ui.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: create dns-ui config.ini
3 | copy:
4 | src: /vagrant/config/config-sample.ini
5 | dest: /vagrant/config/config.ini
6 | remote_src: yes
7 | force: no
8 | register: new_config
9 |
10 | - name: configure config.ini
11 | ini_file:
12 | path: /vagrant/config/config.ini
13 | section: "{{ item.section }}"
14 | option: "{{ item.option }}"
15 | value: "{{ item.value }}"
16 | backup: no
17 | when: new_config.changed
18 | loop:
19 | - { section: "web", option: "baseurl", value: "http://localhost:8080" }
20 | - { section: "database", option: "dsn", value: "\"pgsql:host=127.0.0.1;dbname={{ database_dnsui_schema }}\"" }
21 | - { section: "database", option: "username", value: "{{ database_dnsui_username }}" }
22 | - { section: "database", option: "password", value: "{{ database_dnsui_password }}" }
23 | - { section: "ldap", option: "host", value: "ldap://localhost:389" }
24 | - { section: "ldap", option: "starttls", value: "0" }
25 | - { section: "ldap", option: "dn_user", value: "\"ou=users,{{ ldap_domain_dc }}\"" }
26 | - { section: "ldap", option: "dn_group", value: "\"ou=groups,{{ ldap_domain_dc }}\"" }
27 | - { section: "ldap", option: "bind_dn", value: "\"cn=admin,{{ ldap_domain_dc }}\"" }
28 | - { section: "ldap", option: "bind_password", value: "{{ ldap_admin_password }}" }
29 | - { section: "ldap", option: "admin_group_cn", value: "dnsui_admins" }
30 | - { section: "ldap", option: "user_email", value: "cn" }
31 | - { section: "powerdns", option: "api_url", value: "\"http://localhost:8081/api/v1/servers/localhost\"" }
32 | - { section: "powerdns", option: "api_key", value: "{{ powerdns_api_key }}" }
33 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/ldap.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: generate ldap password for admin
3 | command: slappasswd -s {{ dnsui_admin_password }}
4 | register: cn_admin_password
5 |
6 | - name: generate ldap password for user
7 | command: slappasswd -s {{ dnsui_user_password }}
8 | register: cn_user_password
9 |
10 | - name: create ldap ou's
11 | ldap_entry:
12 | dn: "{{ item }}"
13 | objectClass: organizationalUnit
14 | server_uri: ldap://localhost/
15 | bind_dn: cn=admin,{{ ldap_domain_dc }}
16 | bind_pw: "{{ ldap_admin_password }}"
17 | loop:
18 | - ou=users,{{ ldap_domain_dc }}
19 | - ou=groups,{{ ldap_domain_dc }}
20 |
21 | - name: create ldap users
22 | ldap_entry:
23 | dn: "uid={{ item.user }},ou=users,{{ ldap_domain_dc }}"
24 | objectClass:
25 | - top
26 | - account
27 | - posixAccount
28 | - shadowAccount
29 | attributes:
30 | cn: "{{ item.user }}"
31 | userPassword: "{{ item.password }}"
32 | uidNumber: 1000
33 | gidNumber: 1000
34 | homeDirectory: /tmp
35 | loginShell: /usr/sbin/nologin
36 | shadowLastChange: 0
37 | shadowMin: 0
38 | shadowMax: 99999
39 | shadowWarning: 7
40 | server_uri: ldap://localhost/
41 | bind_dn: cn=admin,{{ ldap_domain_dc }}
42 | bind_pw: "{{ ldap_admin_password }}"
43 | loop:
44 | - { user: "{{ dnsui_admin_username }}", password: "{{ cn_admin_password.stdout }}" } # password: admin
45 | - { user: "{{ dnsui_user_username }}", password: "{{ cn_user_password.stdout }}" } # password: user
46 |
47 | - name: create ldap dnsui_admins posix group
48 | ldap_entry:
49 | dn: cn=dnsui_admins,ou=groups,{{ ldap_domain_dc }}
50 | objectClass:
51 | - posixGroup
52 | - top
53 | attributes:
54 | description: dnsui_admins
55 | gidNumber: 100
56 | memberUid: "{{ dnsui_admin_username }}"
57 | server_uri: ldap://localhost/
58 | bind_dn: cn=admin,{{ ldap_domain_dc }}
59 | bind_pw: "{{ ldap_admin_password }}"
60 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - import_tasks: ping.yml
3 | tags: ping
4 | - import_tasks: config.yml
5 | tags: config
6 | - import_tasks: repo.yml
7 | tags: repo
8 | - import_tasks: packages.yml
9 | tags: packages
10 | - import_tasks: ldap.yml
11 | tags: ldap
12 | - import_tasks: postgresql.yml
13 | tags: postgresql
14 | - import_tasks: powerdns.yml
15 | tags: powerdns
16 | - import_tasks: apache2.yml
17 | tags: apache2
18 | - import_tasks: dns-ui.yml
19 | tags: dns-ui
20 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/packages.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: configure slapd package
3 | debconf:
4 | name: slapd
5 | question: "{{ item.question }}"
6 | vtype: "{{ item.vtype }}"
7 | value: "{{ item.value }}"
8 | loop:
9 | - { question: 'slapd/password1', vtype: 'password', value: '{{ ldap_admin_password }}' }
10 | - { question: 'slapd/password2', vtype: 'password', value: '{{ ldap_admin_password }}' }
11 | - { question: 'slapd/backend', vtype: 'select', value: 'MDB' }
12 | - { question: 'slapd/allow_ldap_v2', vtype: 'boolean', value: 'false' }
13 | - { question: 'shared/organization', vtype: 'string', value: '{{ ldap_organisation }}' }
14 | - { question: 'slapd/move_old_database', vtype: 'boolean', value: 'true' }
15 | - { question: 'slapd/domain', vtype: 'string', value: '{{ ldap_domain }}' }
16 |
17 | - name: install required packages
18 | apt:
19 | name: "{{ item }}"
20 | state: present
21 | update_cache: yes
22 | loop:
23 | - python-ldap
24 | - "{{ ansible_database_package }}"
25 | - apache2
26 | - libapache2-mod-php
27 | - php-intl
28 | - php-json
29 | - php-ldap
30 | - "{{ php_database_package }}"
31 | - php-mbstring
32 | - php-curl
33 | - "{{ database_package }}"
34 | - pdns-server
35 | - pdns-tools
36 | - "{{ powerdns_database_package }}"
37 | - slapd
38 | - ldap-utils
39 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/ping.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: test connection
3 | ping:
4 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/postgresql.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: create postgresql database '{{ database_dnsui_schema }}'
3 | postgresql_db:
4 | name: "{{ database_dnsui_schema }}"
5 | become: true
6 | become_user: postgres
7 |
8 | - name: create postgresql user '{{ database_dnsui_username }}'
9 | postgresql_user:
10 | db: "{{ database_dnsui_schema }}"
11 | name: "{{ database_dnsui_username }}"
12 | password: "{{ database_dnsui_password }}"
13 | become: true
14 | become_user: postgres
15 |
16 | - name: create postgresql database '{{ database_powerdns_schema }}'
17 | postgresql_db:
18 | name: "{{ database_powerdns_schema }}"
19 | become: true
20 | become_user: postgres
21 |
22 | - name: load schema for database '{{ database_powerdns_schema }}'
23 | postgresql_db:
24 | name: "{{ database_powerdns_schema }}"
25 | state: restore
26 | target: /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql
27 | become: true
28 | become_user: postgres
29 |
30 | - name: create postgresql user '{{ database_powerdns_username }}'
31 | postgresql_user:
32 | db: "{{ database_powerdns_schema }}"
33 | name: "{{ database_powerdns_username }}"
34 | password: "{{ database_powerdns_password }}"
35 | become: true
36 | become_user: postgres
37 |
38 | - name: grant all privileges for user '{{ database_powerdns_username }}'
39 | postgresql_privs:
40 | db: "{{ database_powerdns_schema }}"
41 | privs: ALL
42 | type: "{{ item }}"
43 | objs: ALL_IN_SCHEMA
44 | roles: "{{ database_powerdns_username }}"
45 | become: true
46 | become_user: postgres
47 | loop:
48 | - table
49 | - sequence
50 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/powerdns.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: remove unused powerdns configuration
3 | file:
4 | name: "{{ item }}"
5 | state: absent
6 | loop:
7 | - /etc/powerdns/bindbackend.conf
8 | - /etc/powerdns/pdns.d/pdns.simplebind.conf
9 | register: pdns_config_clean
10 |
11 | - name: create powerdns database configuration
12 | template:
13 | src: "{{ powerdns_database_config }}.j2"
14 | dest: /etc/powerdns/pdns.d/{{ powerdns_database_config }}
15 | owner: root
16 | group: root
17 | mode: 0644
18 | register: pdns_db_config
19 |
20 | - name: configure powerdns
21 | lineinfile:
22 | path: /etc/powerdns/pdns.conf
23 | regexp: "^{{ item.name }}="
24 | line: "{{ item.name }}={{ item.value }}"
25 | loop:
26 | - { name: "default-soa-name", value: "{{ powerdns_default_soa_name }}" }
27 | - { name: "default-soa-mail", value: "{{ powerdns_default_soa_mail }}" }
28 | - { name: "{{ powerdns_default_ksk_algorithm_setting }}", value: "{{ powerdns_default_ksk_algorithm }}" }
29 | - { name: "webserver", value: "yes" }
30 | - { name: "webserver-address", value: "0.0.0.0" }
31 | - { name: "webserver-allow-from", value: "127.0.0.1" }
32 | - { name: "webserver-port", value: "8081" }
33 | - { name: "api", value: "yes" }
34 | - { name: "api-key", value: "{{ powerdns_api_key }}" }
35 | register: pdns_config
36 |
37 | - name: restart powerdns
38 | service:
39 | name: pdns
40 | state: restarted
41 | when: pdns_config_clean.changed or pdns_db_config.changed or pdns_config.changed
42 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/tasks/repo.yml:
--------------------------------------------------------------------------------
1 | - name: add powerdns repository key
2 | apt_key:
3 | url: "{{ powerdns_repo_key }}"
4 | state: present
5 |
6 | - name: add powerdns repository
7 | apt_repository:
8 | repo: "{{ powerdns_repo }}"
9 | state: present
10 | filename: powerdns
11 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/templates/apache2.conf.j2:
--------------------------------------------------------------------------------
1 |
2 | DocumentRoot /vagrant/public_html
3 |
4 | CustomLog /var/log/apache2/dns-ui-access.log combined
5 | ErrorLog /var/log/apache2/dns-ui-error.log
6 |
7 | DirectoryIndex init.php
8 | FallbackResource /init.php
9 | AllowEncodedSlashes NoDecode
10 |
11 |
12 | AuthType Basic
13 | AuthName "{{ dnsui_auth_name }}"
14 | AuthBasicProvider ldap
15 |
16 | AuthLDAPURL "ldap://localhost:389/ou=users,{{ ldap_domain_dc }}?uid?sub?(objectClass=*)"
17 | Require valid-user
18 |
19 |
20 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/templates/pdns.gpgsql.conf.j2:
--------------------------------------------------------------------------------
1 | launch=gpgsql
2 | gpgsql-host=127.0.0.1
3 | gpgsql-user={{ database_powerdns_username }}
4 | gpgsql-dbname={{ database_powerdns_schema }}
5 | gpgsql-password={{ database_powerdns_password }}
6 | gpgsql-dnssec=yes
7 |
--------------------------------------------------------------------------------
/vagrant/ansible/roles/dns-ui/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | supported_db_vendors:
3 | - postgresql
4 |
--------------------------------------------------------------------------------
/views/csrf.php:
--------------------------------------------------------------------------------
1 | set('address', $relative_request_url);
20 | $content->set('fulladdress', $absolute_request_url);
21 |
22 | $page = new PageSection('base');
23 | $page->set('title', 'Form submission failed');
24 | $page->set('content', $content);
25 | $page->set('alerts', array());
26 | echo $page->generate();
27 |
--------------------------------------------------------------------------------
/views/error403.php:
--------------------------------------------------------------------------------
1 | set('address', $relative_request_url);
20 | $content->set('fulladdress', $absolute_request_url);
21 |
22 | $page = new PageSection('base');
23 | $page->set('title', 'Access denied');
24 | $page->set('content', $content);
25 | $page->set('alerts', array());
26 | header('HTTP/1.1 403 Forbidden');
27 | echo $page->generate();
28 |
--------------------------------------------------------------------------------
/views/error404.php:
--------------------------------------------------------------------------------
1 | set('address', $relative_request_url);
20 | $content->set('fulladdress', $absolute_request_url);
21 | $content->set('referrer', isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
22 |
23 | $page = new PageSection('base');
24 | $page->set('title', 'Page not found');
25 | $page->set('content', $content);
26 | $page->set('alerts', array());
27 | header('HTTP/1.1 404 Not Found');
28 | echo $page->generate();
29 |
--------------------------------------------------------------------------------
/views/error500.php:
--------------------------------------------------------------------------------
1 | set('error_number', $error_number);
20 | if(isset($active_user) && is_object($active_user) && isset($e)) {
21 | if($active_user->developer) {
22 | $content->set('exception_class', get_class($e));
23 | $content->set('error_details', $e);
24 | }
25 | }
26 |
27 | $page = new PageSection('base');
28 | $page->set('title', 'An error occurred');
29 | $page->set('content', $content);
30 | $page->set('alerts', array());
31 | header('HTTP/1.1 500 Internal Server Error');
32 | echo $page->generate();
33 |
--------------------------------------------------------------------------------
/views/error503.php:
--------------------------------------------------------------------------------
1 | set('title', 'Down for maintenance');
22 | $page->set('content', $content);
23 | $page->set('alerts', array());
24 | header('HTTP/1.1 503 Service Unavailable');
25 | echo $page->generate();
26 |
--------------------------------------------------------------------------------
/views/error503_upstream.php:
--------------------------------------------------------------------------------
1 | set('title', 'DNS server communication failure');
22 | $page->set('content', $content);
23 | $page->set('alerts', array());
24 | header('HTTP/1.1 503 Service Unavailable');
25 | echo $page->generate();
26 |
--------------------------------------------------------------------------------
/views/home.php:
--------------------------------------------------------------------------------
1 | admin) {
19 | require('views/error403.php');
20 | exit;
21 | }
22 |
23 | $replication_types = $replication_type_dir->list_replication_types();
24 | $ns_templates = $template_dir->list_ns_templates();
25 | $soa_templates = $template_dir->list_soa_templates();
26 |
27 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
28 | if(isset($_POST['update_settings'])) {
29 | if($_POST['default_replication_type'] === '') {
30 | $type = null;
31 | } else {
32 | $type = $replication_type_dir->get_replication_type_by_id($_POST['default_replication_type']);
33 | }
34 | $replication_type_dir->set_default_replication_type($type);
35 |
36 | if($_POST['default_soa_template'] === '') {
37 | $template = null;
38 | } else {
39 | $template = $template_dir->get_soa_template_by_id($_POST['default_soa_template']);
40 | }
41 | $template_dir->set_default_soa_template($template);
42 |
43 | if($_POST['default_ns_template'] === '') {
44 | $template = null;
45 | } else {
46 | $template = $template_dir->get_ns_template_by_id($_POST['default_ns_template']);
47 | }
48 | $template_dir->set_default_ns_template($template);
49 |
50 | $alert = new UserAlert;
51 | $alert->content = "Settings updated.";
52 | $active_user->add_alert($alert);
53 |
54 | redirect();
55 | }
56 | }
57 |
58 | $content = new PageSection('settings');
59 | $content->set('replication_types', $replication_types);
60 | $content->set('ns_templates', $ns_templates);
61 | $content->set('soa_templates', $soa_templates);
62 |
63 | $page = new PageSection('base');
64 | $page->set('title', 'Settings');
65 | $page->set('content', $content);
66 | $page->set('alerts', $active_user->pop_alerts());
67 |
68 | echo $page->generate();
69 |
--------------------------------------------------------------------------------
/views/template.php:
--------------------------------------------------------------------------------
1 | admin) {
19 | require('views/error403.php');
20 | die;
21 | }
22 |
23 | $type = $router->vars['type'];
24 | $name = $router->vars['name'];
25 |
26 | try {
27 | if($type == 'soa') {
28 | $template = $template_dir->get_soa_template_by_name($name);
29 | } elseif($type == 'ns') {
30 | $template = $template_dir->get_ns_template_by_name($name);
31 | } else {
32 | require('views/error404.php');
33 | die;
34 | }
35 | } catch(TemplateNotFound $e) {
36 | require('views/error404.php');
37 | die;
38 | }
39 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
40 | if(isset($_POST['update_template'])) {
41 | $template->name = trim($_POST['name']);
42 | if($type == 'soa') {
43 | $template->primary_ns = $_POST['primary_ns'];
44 | $template->contact = $_POST['contact'];
45 | $template->refresh = DNSTime::expand($_POST['refresh']);
46 | $template->retry = DNSTime::expand($_POST['retry']);
47 | $template->expire = DNSTime::expand($_POST['expire']);
48 | $template->default_ttl = DNSTime::expand($_POST['default_ttl']);
49 | $template->soa_ttl = DNSTime::expand($_POST['soa_ttl']);
50 | } elseif($type == 'ns') {
51 | $template->nameservers = implode("\n", preg_split('/[,\s]+/', trim($_POST['nameservers'])));
52 | }
53 | $template->update();
54 | $alert = new UserAlert;
55 | $alert->content = "Template updated.";
56 | $active_user->add_alert($alert);
57 | }
58 | redirect('/templates/'.urlencode($type).'/'.urlencode($template->name));
59 | }
60 |
61 | $content = new PageSection('template');
62 | $content->set('type', $type);
63 | $content->set('template', $template);
64 |
65 | $page = new PageSection('base');
66 | $page->set('title', strtoupper($type).' template: '.$name);
67 | $page->set('content', $content);
68 | $page->set('alerts', $active_user->pop_alerts());
69 |
70 | echo $page->generate();
71 |
--------------------------------------------------------------------------------
/views/templates.php:
--------------------------------------------------------------------------------
1 | admin) {
19 | require('views/error403.php');
20 | exit;
21 | }
22 |
23 | $type = null;
24 | $title = 'Templates';
25 | if(isset($router->vars['type'])) {
26 | $type = $router->vars['type'];
27 | $title = strtoupper($type).' templates';
28 | }
29 |
30 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
31 | if(isset($_POST['create_template'])) {
32 | if($type == 'soa') {
33 | $template = new SOATemplate;
34 | $template->name = trim($_POST['name']);
35 | $template->primary_ns = $_POST['primary_ns'];
36 | $template->contact = $_POST['contact'];
37 | $template->refresh = DNSTime::expand($_POST['refresh']);
38 | $template->retry = DNSTime::expand($_POST['retry']);
39 | $template->expire = DNSTime::expand($_POST['expire']);
40 | $template->default_ttl = DNSTime::expand($_POST['default_ttl']);
41 | $template->soa_ttl = DNSTime::expand($_POST['soa_ttl']);
42 | } elseif($type == 'ns') {
43 | $template = new NSTemplate;
44 | $template->name = trim($_POST['name']);
45 | $template->nameservers = implode("\n", preg_split('/[,\s]+/', trim($_POST['nameservers'])));
46 | }
47 | $template_dir->add_template($template);
48 | $alert = new UserAlert;
49 | $alert->content = "Template created.";
50 | $active_user->add_alert($alert);
51 | } elseif(isset($_POST['set_default_soa_template'])) {
52 | $template = $template_dir->get_soa_template_by_id($_POST['set_default_soa_template']);
53 | $template_dir->set_default_soa_template($template);
54 | $alert = new UserAlert;
55 | $alert->content = "New SOA default set.";
56 | $active_user->add_alert($alert);
57 | } elseif(isset($_POST['set_default_ns_template'])) {
58 | $template = $template_dir->get_ns_template_by_id($_POST['set_default_ns_template']);
59 | $template_dir->set_default_ns_template($template);
60 | $alert = new UserAlert;
61 | $alert->content = "New NS default set.";
62 | $active_user->add_alert($alert);
63 | } elseif(isset($_POST['delete_soa_template'])) {
64 | $template = $template_dir->get_soa_template_by_id($_POST['delete_soa_template']);
65 | $template_dir->delete_template($template);
66 | $alert = new UserAlert;
67 | $alert->content = "SOA template deleted.";
68 | $active_user->add_alert($alert);
69 | } elseif(isset($_POST['delete_ns_template'])) {
70 | $template = $template_dir->get_ns_template_by_id($_POST['delete_ns_template']);
71 | $template_dir->delete_template($template);
72 | $alert = new UserAlert;
73 | $alert->content = "NS template deleted.";
74 | $active_user->add_alert($alert);
75 | }
76 | redirect('/templates/'.urlencode($type).'#list');
77 | }
78 | $soa_templates = $template_dir->list_soa_templates();
79 | $ns_templates = $template_dir->list_ns_templates();
80 |
81 | $content = new PageSection('templates');
82 | $content->set('title', $title);
83 | $content->set('soa_templates', $soa_templates);
84 | $content->set('ns_templates', $ns_templates);
85 | $content->set('type', $type);
86 |
87 | $page = new PageSection('base');
88 | $page->set('title', $title);
89 | $page->set('content', $content);
90 | $page->set('alerts', $active_user->pop_alerts());
91 |
92 | echo $page->generate();
93 |
--------------------------------------------------------------------------------
/views/user.php:
--------------------------------------------------------------------------------
1 | get_user_by_uid($router->vars['uid']);
20 | } catch(UserNotFound $e) {
21 | require('views/error404.php');
22 | die;
23 | }
24 |
25 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
26 | if(isset($_POST['update_user']) && $active_user->admin) {
27 | $user->name = $_POST['name'];
28 | $user->email = $_POST['email'];
29 | $user->active = isset($_POST['active']) ? 1 : 0;
30 | $user->admin = isset($_POST['admin']) ? 1 : 0;
31 | $user->update();
32 | $alert = new UserAlert;
33 | $alert->content = "User '{$user->uid}' updated.";
34 | $alert->class = 'success';
35 | $active_user->add_alert($alert);
36 | redirect();
37 | }
38 | }
39 | $changesets = $user->list_changesets();
40 | $zones = $active_user->list_accessible_zones();
41 | $visible_changesets = array();
42 | foreach($changesets as $changeset) {
43 | if(isset($zones[$changeset->zone->pdns_id])) {
44 | $visible_changesets[] = $changeset;
45 | }
46 | }
47 | if(count($visible_changesets) == 0 && !$active_user->admin) {
48 | require('views/error404.php');
49 | die;
50 | }
51 |
52 | $content = new PageSection('user');
53 | $content->set('user', $user);
54 | $content->set('changesets', $visible_changesets);
55 |
56 | $page = new PageSection('base');
57 | $page->set('title', $user->name);
58 | $page->set('content', $content);
59 | $page->set('alerts', $active_user->pop_alerts());
60 |
61 | echo $page->generate();
62 |
--------------------------------------------------------------------------------
/views/users.php:
--------------------------------------------------------------------------------
1 | list_users();
19 |
20 | if(!$active_user->admin) {
21 | require('views/error403.php');
22 | die;
23 | }
24 |
25 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
26 | if(isset($_POST['add_user']) && $active_user->admin) {
27 | $user = new User;
28 | $user->auth_realm = 'local';
29 | $user->uid = $_POST['uid'];
30 | $user->name = $_POST['name'];
31 | $user->email = $_POST['email'];
32 | $user->active = 1;
33 | $user->admin = isset($_POST['admin']) ? 1 : 0;
34 | try {
35 | $user_dir->add_user($user);
36 | $alert = new UserAlert;
37 | $alert->content = 'User \''.hesc($user->uid).' \' added.';
38 | $alert->escaping = ESC_NONE;
39 | $alert->class = 'success';
40 | $active_user->add_alert($alert);
41 | } catch(UserAlreadyExistsException $e) {
42 | $alert = new UserAlert;
43 | $alert->content = 'A user with user ID of \''.hesc($user->uid).' \' already exists.';
44 | $alert->escaping = ESC_NONE;
45 | $alert->class = 'danger';
46 | $active_user->add_alert($alert);
47 | }
48 | redirect();
49 | }
50 | }
51 |
52 | $content = new PageSection('users');
53 | $content->set('users', $users);
54 |
55 | $page = new PageSection('base');
56 | $page->set('title', 'Users');
57 | $page->set('content', $content);
58 | $page->set('alerts', $active_user->pop_alerts());
59 |
60 | echo $page->generate();
61 |
--------------------------------------------------------------------------------
/views/zoneexport.php:
--------------------------------------------------------------------------------
1 | get_zone_by_name($router->vars['name'].'.');
20 | } catch(ZoneNotFound $e) {
21 | require('views/error404.php');
22 | exit;
23 | }
24 |
25 | if(!$active_user->admin && !$active_user->access_to($zone)) {
26 | require('views/error403.php');
27 | exit;
28 | }
29 |
30 | $rrsets = $zone->list_resource_record_sets();
31 |
32 | $page = new PageSection('zoneexport');
33 | $page->set('zone', $zone);
34 | $page->set('rrsets', $rrsets);
35 | header('Content-type: text/plain; charset=utf-8');
36 | header('Content-disposition: attachment; filename='.DNSZoneName::unqualify($zone->name));
37 | echo $page->generate();
38 |
--------------------------------------------------------------------------------
/views/zoneimport.php:
--------------------------------------------------------------------------------
1 | get_zone_by_name($router->vars['name'].'.');
20 | } catch(ZoneNotFound $e) {
21 | require('views/error404.php');
22 | exit;
23 | }
24 |
25 | if(!$active_user->admin && !$active_user->access_to($zone)) {
26 | require('views/error403.php');
27 | exit;
28 | }
29 |
30 | if(isset($_FILES['zonefile'])) {
31 | $lines = file_get_contents($_FILES['zonefile']['tmp_name']);
32 | $zonefile = new BindZonefile($lines);
33 | try {
34 | $new_rrsets = $zonefile->parse_into_rrsets($zone, $_POST['comment_handling']);
35 | $modifications = merge_rrsets($zone, $new_rrsets);
36 | } catch(ZoneImportError $e) {
37 | $content = new PageSection('zone_update_failed');
38 | $content->set('message', $e->getMessage());
39 | }
40 | if(!isset($content)) {
41 | $content = new PageSection('zoneimport');
42 | $content->set('zone', $zone);
43 | $content->set('modifications', $modifications);
44 | }
45 | } else {
46 | redirect('/zones/'.urlencode($zone->name));
47 | }
48 |
49 | $page = new PageSection('base');
50 | $page->set('title', 'Import preview for '.DNSZoneName::unqualify(punycode_to_utf8($zone->name)).' zone update');
51 | $page->set('content', $content);
52 | $page->set('alerts', $active_user->pop_alerts());
53 |
54 | echo $page->generate();
55 |
56 | function merge_rrsets($zone, $new_rrsets) {
57 | global $active_user;
58 | // Compare existing content with new content and collate the differences
59 | $rrsets = $zone->list_resource_record_sets();
60 | $old_rrsets = $rrsets;
61 | $modifications = array('add' => array(), 'update' => array(), 'delete' => array());
62 | foreach($new_rrsets as $ref => $new_rrset) {
63 | if($new_rrset->type == 'SOA') continue;
64 | if(isset($old_rrsets[$ref])) {
65 | $old_rrset = $old_rrsets[$ref];
66 | $old_rrs = $old_rrset->list_resource_records();
67 | $new_rrs = $new_rrset->list_resource_records();
68 | $old_comment = $old_rrset->merge_comment_text();
69 | $new_comment = $new_rrset->merge_comment_text();
70 | $rrset_modifications = array();
71 | if($old_rrset->ttl != $new_rrset->ttl) {
72 | $rrset_modifications[] = 'TTL changed from '.DNSTime::abbreviate($old_rrset->ttl).' to '.DNSTime::abbreviate($new_rrset->ttl);
73 | }
74 | foreach($new_rrs as $new_rr) {
75 | $rr_match = false;
76 | foreach($old_rrs as $rr_ref => $old_rr) {
77 | if($new_rr->content == $old_rr->content) {
78 | $rr_match = true;
79 | unset($old_rrs[$rr_ref]);
80 | break;
81 | }
82 | }
83 | if($rr_match) {
84 | if($new_rr->disabled && !$old_rr->disabled) {
85 | $rrset_modifications[] = 'Disabled RR: '.$new_rr->content;
86 | }
87 | if(!$new_rr->disabled && $old_rr->disabled) {
88 | $rrset_modifications[] = 'Enabled RR: '.$new_rr->content;
89 | }
90 | } else {
91 | // New RR
92 | $rrset_modifications[] = 'New RR: '.$new_rr->content;
93 | }
94 | }
95 | foreach($old_rrs as $old_rr) {
96 | // Deleted RR
97 | $rrset_modifications[] = 'Deleted RR: '.$old_rr->content;
98 | }
99 | $new_rrset->clear_comments();
100 | if($old_comment == $new_comment) {
101 | foreach($old_rrset->list_comments() as $comment) {
102 | $new_rrset->add_comment($comment);
103 | }
104 | } else {
105 | if($old_comment != '') $rrset_modifications[] = 'Deleted comment: '.$old_comment;
106 | if($new_comment != '') {
107 | $rrset_modifications[] = 'New comment: '.$new_comment;
108 | $comment = new Comment;
109 | $comment->content = $new_comment;
110 | $comment->account = $active_user->uid;
111 | $new_rrset->add_comment($comment);
112 | }
113 | }
114 | if(count($rrset_modifications) > 0) {
115 | $modifications['update'][$ref] = array();
116 | $modifications['update'][$ref]['new'] = $new_rrset;
117 | $modifications['update'][$ref]['changelist'] = $rrset_modifications;
118 | $modifications['update'][$ref]['json'] = build_json('update', $new_rrset, $zone->name);
119 | }
120 | } else {
121 | // New RRSet
122 | $modifications['add'][$ref] = array();
123 | $modifications['add'][$ref]['new'] = $new_rrset;
124 | $modifications['add'][$ref]['json'] = build_json('add', $new_rrset, $zone->name);
125 | }
126 | }
127 | foreach($old_rrsets as $ref => $old_rrset) {
128 | if($old_rrset->type == 'SOA') continue;
129 | if(!isset($new_rrsets[$ref])) {
130 | // Deleted RRSet
131 | $modifications['delete'][$ref] = array();
132 | $modifications['delete'][$ref]['old'] = $old_rrset;
133 | $modifications['delete'][$ref]['json'] = build_json('delete', $old_rrset, $zone->name);
134 | }
135 | }
136 | return $modifications;
137 | }
138 |
139 | function build_json($action, $rrset, $zonename) {
140 | $data = new StdClass;
141 | $data->action = $action;
142 | $data->name = DNSName::abbreviate($rrset->name, $zonename);
143 | $data->type = $rrset->type;
144 | $data->ttl = $rrset->ttl;
145 | if($action != 'add') {
146 | $data->oldname = $data->name;
147 | $data->oldtype = $data->type;
148 | }
149 | if($action != 'delete') {
150 | $data->records = array();
151 | foreach($rrset->list_resource_records() as $rr) {
152 | $rr_data = new StdClass;
153 | $rr_data->content = DNSContent::decode($rr->content, $rrset->type, $zonename);
154 | $rr_data->enabled = $rr->disabled ? 'No' : 'Yes';
155 | $data->records[] = $rr_data;
156 | }
157 | $data->comment = $rrset->merge_comment_text();
158 | }
159 | return json_encode($data);
160 | }
161 |
162 |
--------------------------------------------------------------------------------
/views/zones.php:
--------------------------------------------------------------------------------
1 | list_accessible_zones(array('pending_updates'));
19 | usort($zones, function($a, $b) {
20 | $aname = implode(',', array_reverse(explode('.', punycode_to_utf8($a->name))));
21 | $bname = implode(',', array_reverse(explode('.', punycode_to_utf8($b->name))));
22 | return strnatcasecmp($aname, $bname);
23 | });
24 |
25 | $replication_types = $replication_type_dir->list_replication_types();
26 | $soa_templates = $template_dir->list_soa_templates();
27 | $ns_templates = $template_dir->list_ns_templates();
28 | $account_whitelist = !empty($config['dns']['classification_whitelist']) ? explode(',', $config['dns']['classification_whitelist']) : [];
29 | $force_account_whitelist = !empty($config['dns']['classification_whitelist']) ? 1 : 0;
30 |
31 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
32 | if(isset($_POST['add_zone']) && $active_user->admin) {
33 | $zonename = utf8_to_punycode(rtrim(trim($_POST['name']), '.')).'.';
34 | if(strlen($zonename) == 1) {
35 | $content = new PageSection('zone_add_failed');
36 | $content->set('message', 'No zone name given.');
37 | } else {
38 | $zone = new Zone;
39 | $zone->name = $zonename;
40 | $zone->account = trim($_POST['classification']);
41 | $zone->dnssec = isset($_POST['dnssec']) ? 1 : 0;
42 | $zone->kind = $_POST['kind'];
43 | $zone->nameservers = array();
44 | foreach(preg_split('/[,\s]+/', $_POST['nameservers']) as $nameserver) {
45 | $zone->nameservers[] = $nameserver;
46 | }
47 | $soa = new ResourceRecord;
48 | $soa->content = "$_POST[primary_ns] $_POST[contact] ".date('Ymd00')." ".DNSTime::expand($_POST['refresh'])." ".DNSTime::expand($_POST['retry'])." ".DNSTime::expand($_POST['expire'])." ".DNSTime::expand($_POST['default_ttl']);
49 | $soa->disabled = false;
50 | $soaset = new ResourceRecordSet;
51 | $soaset->name = $zonename;
52 | $soaset->type = 'SOA';
53 | $soaset->ttl = DNSTime::expand($_POST['soa_ttl']);
54 | $soaset->add_resource_record($soa);
55 | $zone->add_resource_record_set($soaset);
56 | try {
57 | $zone_dir->create_zone($zone);
58 | redirect('/zones/'.urlencode(DNSZoneName::unqualify($zonename)));
59 | } catch(Pest_InvalidRecord $e) {
60 | $content = new PageSection('zone_add_failed');
61 | $content->set('message', json_decode($e->getMessage())->error);
62 | }
63 | }
64 | }
65 | }
66 |
67 | if(!isset($content)) {
68 | $content = new PageSection('zones');
69 | $content->set('zones', $zones);
70 | $content->set('replication_types', $replication_types);
71 | $content->set('soa_templates', $soa_templates);
72 | $content->set('ns_templates', $ns_templates);
73 | $content->set('dnssec_enabled', isset($config['dns']['dnssec']) ? $config['dns']['dnssec'] : '0');
74 | $content->set('dnssec_edit', isset($config['dns']['dnssec_edit']) ? $config['dns']['dnssec_edit'] : '0');
75 | $content->set('account_whitelist', $account_whitelist);
76 | $content->set('force_account_whitelist', $force_account_whitelist);
77 | }
78 |
79 | $page = new PageSection('base');
80 | $page->set('title', 'Zones');
81 | $page->set('content', $content);
82 | $page->set('alerts', $active_user->pop_alerts());
83 |
84 | echo $page->generate();
85 |
--------------------------------------------------------------------------------
/views/zonesplit.php:
--------------------------------------------------------------------------------
1 | get_zone_by_name($router->vars['name'].'.');
20 | } catch(ZoneNotFound $e) {
21 | require('views/error404.php');
22 | exit;
23 | }
24 |
25 | if(!$active_user->admin) {
26 | require('views/error403.php');
27 | exit;
28 | }
29 |
30 | $rrsets = $zone->list_resource_record_sets();
31 |
32 | if($_SERVER['REQUEST_METHOD'] == 'POST') {
33 | if(isset($_POST['suffix'])) {
34 | $newzonename = utf8_to_punycode($_POST['suffix']).'.'.$zone->name;
35 | $split = array();
36 | $cname_error = false;
37 | foreach($rrsets as $rrset) {
38 | if((stripos($rrset->name, '.'.$newzonename) === strlen($rrset->name) - strlen('.'.$newzonename)
39 | || ($rrset->name == $newzonename && $rrset->type != 'NS')) && $rrset->type != 'SOA') {
40 | if($rrset->name == $newzonename && $rrset->type == 'CNAME') {
41 | $cname_error = true;
42 | $alert = new UserAlert;
43 | $alert->content = "It is not possible to have a CNAME record at the root of the zone. You will need to change the highlighted CNAME record into an A/AAAA record before proceeding.";
44 | $alert->class = "danger";
45 | $active_user->add_alert($alert);
46 | }
47 | $split[] = $rrset;
48 | }
49 | }
50 | if(isset($_POST['confirm']) && count($split) > 0 && !$cname_error) {
51 | // Build new zone with split records
52 | // Copy nameservers and SOA from old zone
53 | $newzone = new Zone;
54 | $newzone->name = $newzonename;
55 | $newzone->account = $zone->account;
56 | $newzone->dnssec = $zone->dnssec;
57 | $newzone->kind = 'Master';
58 | $newzone->nameservers = $zone->nameservers;
59 | foreach($split as $rrset) {
60 | $newzone->add_resource_record_set($rrset);
61 | }
62 | $soa = new ResourceRecord;
63 | $soa->content = $zone->soa->content;
64 | $soa->disabled = false;
65 | $soaset = new ResourceRecordSet;
66 | $soaset->name = $newzonename;
67 | $soaset->type = 'SOA';
68 | $soaset->ttl = $zone->soa->ttl;
69 | $soaset->add_resource_record($soa);
70 | $newzone->add_resource_record_set($soaset);
71 | try {
72 | // Create new zone
73 | $zone_dir->create_zone($newzone);
74 | // Update old zone (remove split records)
75 | $changes = array();
76 | foreach($split as $rrset) {
77 | $change = new Change;
78 | $change->before = serialize($rrset);
79 | $changes[] = $change;
80 | $zone->delete_resource_record_set($rrset);
81 | }
82 | $zone->commit_changes();
83 | $changeset = new ChangeSet;
84 | if(!empty($_POST['comment'])) {
85 | $changeset->comment = $_POST['comment'];
86 | }
87 | $zone->add_changeset($changeset);
88 | foreach($changes as $change) {
89 | $changeset->add_change($change);
90 | }
91 | // Create git commit for this change
92 | $git_commit_comment = "Zone {$newzonename} split off from {$zone->name} via DNS UI\n";
93 | $git_commit_comment .= "\n{$config['web']['baseurl']}/zones/".urlencode($zone->name).'#changelog#'.$changeset->id;
94 | if(!empty($_POST['comment'])) {
95 | $git_commit_comment .= "\nChange comment: {$_POST['comment']}";
96 | }
97 | $zone_dir->git_tracked_export(array($zone), $git_commit_comment);
98 | $alert = new UserAlert;
99 | $alert->content = "Zone split of ".DNSZoneName::unqualify($newzonename)." from ".DNSZoneName::unqualify($zone->name)." has been completed.";
100 | $active_user->add_alert($alert);
101 | $content = new PageSection('zonesplitcompleted');
102 | $content->set('zone', $zone);
103 | $content->set('newzonename', $newzonename);
104 | } catch(Pest_InvalidRecord $e) {
105 | $content = new PageSection('zone_add_failed');
106 | $content->set('message', json_decode($e->getMessage())->error);
107 | }
108 | } else {
109 | $content = new PageSection('zonesplit');
110 | $content->set('zone', $zone);
111 | $content->set('newzonename', $newzonename);
112 | $content->set('suffix', $_POST['suffix']);
113 | $content->set('split', $split);
114 | $content->set('cname_error', $cname_error);
115 | }
116 | }
117 | }
118 |
119 | $page = new PageSection('base');
120 | $page->set('title', 'Split preview for '.$zone->name);
121 | $page->set('content', $content);
122 | $page->set('alerts', $active_user->pop_alerts());
123 |
124 | echo $page->generate();
125 |
126 |
--------------------------------------------------------------------------------