Skip to content

Commit 0a329cd

Browse files
author
Marius Burkard
committed
- added dns bulk editor, contributed by Timme Hosting
1 parent 336b838 commit 0a329cd

File tree

9 files changed

+568
-3
lines changed

9 files changed

+568
-3
lines changed
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
<?php
2+
3+
/*
4+
Copyright (c) 2008, Till Brehm, projektfarm Gmbh
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification,
8+
are permitted provided that the following conditions are met:
9+
10+
* Redistributions of source code must retain the above copyright notice,
11+
this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
* Neither the name of ISPConfig nor the names of its contributors
16+
may be used to endorse or promote products derived from this software without
17+
specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22+
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
require_once '../../lib/config.inc.php';
32+
require_once '../../lib/app.inc.php';
33+
34+
//* Check permissions for module
35+
$app->auth->check_module_permissions('dns');
36+
37+
// Loading the template
38+
$app->uses('tpl,tfrom_base,validate_dns,functions');
39+
$app->tpl->newTemplate("form.tpl.htm");
40+
41+
include 'lib/lang/'.$_SESSION['s']['language'].'_dns_bulk_editor.lng';
42+
$app->tpl->setVar($wb);
43+
44+
// Load clients (if admin):
45+
46+
if ($app->auth->is_admin()) {
47+
$clients = $app->db->queryAllRecords("SELECT sys_group.groupid,CONCAT(IF(client.company_name != '', CONCAT(client.company_name, ' :: '), ''), IF(client.contact_firstname != '', CONCAT(client.contact_firstname, ' '), ''), client.contact_name, ' (', client.username, IF(client.customer_no != '', CONCAT(', ', client.customer_no), ''), ')') as name FROM sys_group, client WHERE sys_group.groupid != 1 AND sys_group.client_id = client.client_id ORDER BY client.company_name, client.contact_name");
48+
49+
$clients_select_options = '<option value="">'.$wb['select_client_txt'].'</option>';
50+
51+
foreach($clients as $client) {
52+
$selected = (intval($_POST["client_group_id"]) == $client['groupid'])?'SELECTED':'';
53+
$clients_select_options .= "<option value='$client[groupid]' $selected>$client[name]</option>\r\n";
54+
}
55+
56+
$app->tpl->setVar('clients_select_options', $clients_select_options);
57+
}
58+
59+
// Load zones:
60+
61+
if ($app->auth->is_admin()) {
62+
if (isset($_POST["client_group_id"])) {
63+
$client_group_ids = intval($_POST["client_group_id"]);
64+
}
65+
} else {
66+
$client_group_ids = $_SESSION['s']['user']['groups'];
67+
}
68+
69+
if(isset($client_group_ids)) {
70+
$sql = 'SELECT id, origin FROM dns_soa WHERE sys_groupid IN ('.$client_group_ids.') AND '.$app->tform_base->getAuthSQL('u');
71+
72+
$zones = $app->db->queryAllRecords($sql);
73+
74+
$zones_rows = array(); // All zones (for output)
75+
76+
foreach ($zones as $zone) {
77+
$zones_rows[] = array(
78+
'zone_id'=>$zone['id'],
79+
'zone_name'=>$zone['origin'],
80+
'zone_selected'=>isset($_POST['zone_'.$zone['id']]),
81+
);
82+
83+
}
84+
85+
$app->tpl->setLoop('zones_rows', $zones_rows);
86+
$app->tpl->setVar('zones_rows_count', count($zones_rows));
87+
88+
$update_zones = array(); // Currently selected zones in form (if any)
89+
90+
foreach ($zones as $zone) {
91+
if (isset($_POST['zone_'.$zone['id']])) {
92+
$update_zones[$zone['id']] = $zone['origin'];
93+
}
94+
}
95+
} else {
96+
$app->tpl->setVar('zones_rows_count', 0);
97+
}
98+
99+
if (isset($_GET['submitted'])) {
100+
validate_and_update($update_zones);
101+
}
102+
103+
$app->tpl_defaults();
104+
105+
if (isset($result)) {
106+
$app->tpl->setVar('result', $result);
107+
$app->tpl->setInclude('content_tpl', 'templates/dns_bulk_editor_result.htm');
108+
} else {
109+
$app->tpl->setInclude('content_tpl', 'templates/dns_bulk_editor.htm');
110+
}
111+
112+
$app->tpl->pparse();
113+
114+
function validate_and_update($update_zones) {
115+
global $app, $wb, $client_group_ids, $result;
116+
117+
// Validate:
118+
119+
if ($client_group_ids == 0) {
120+
$app->tpl->setVar('error', $wb['error_no_client_txt']);
121+
return;
122+
}
123+
124+
if (!isset($_POST['action'])) {
125+
$app->tpl->setVar('error', $wb['error_no_action_txt']);
126+
return;
127+
}
128+
129+
switch ($_POST['action']) {
130+
case 'a_records':
131+
$app->tpl->setVar('action_a_records', true);
132+
$app->tpl->setVar('a_records_search', htmlspecialchars($_POST['a_records_search']));
133+
$app->tpl->setVar('a_records_replace', htmlspecialchars($_POST['a_records_replace']));
134+
135+
if (!validate_ips($_POST['a_records_search'], $_POST['a_records_replace'])) {
136+
// Error message is set in validate_ips
137+
return;
138+
}
139+
140+
break;
141+
case 'mx_records':
142+
$app->tpl->setVar('action_mx_records', true);
143+
$app->tpl->setVar('mx_records_search', htmlspecialchars($_POST['mx_records_search']));
144+
$app->tpl->setVar('mx_records_replace', htmlspecialchars($_POST['mx_records_replace']));
145+
146+
if (!validate_zone($_POST['mx_records_search']) || !validate_zone($_POST['mx_records_replace'])) {
147+
$app->tpl->setVar('error', $wb['error_invalid_dns_zone_txt']);
148+
return;
149+
}
150+
151+
break;
152+
case 'ttl':
153+
$app->tpl->setVar('action_ttl', true);
154+
$app->tpl->setVar('ttl', htmlspecialchars($_POST['ttl']));
155+
156+
if (trim($_POST['ttl']) == '' || !is_numeric($_POST['ttl']) || intval($_POST['ttl']) < 60) {
157+
$app->tpl->setVar('error', $wb['error_no_ttl_txt']);
158+
return;
159+
}
160+
161+
break;
162+
}
163+
164+
if (!(isset($update_zones) && count($update_zones) > 0)) {
165+
$app->tpl->setVar('error', $wb['error_no_zone_txt']);
166+
return;
167+
}
168+
169+
foreach ($update_zones as $id=>$origin) {
170+
$sql = 'SELECT id FROM dns_soa WHERE id = ? AND '.$app->tform_base->getAuthSQL('u');
171+
if (!is_array($app->db->queryOneRecord($sql, $id))) {
172+
$app->tpl->setVar('error', $wb['error_invalid_zone_txt']);
173+
return;
174+
}
175+
}
176+
177+
// Update:
178+
179+
switch ($_POST['action']) {
180+
case 'a_records':
181+
$result = '<h3>'.$wb['a_records_txt'].'</h3>';
182+
183+
foreach ($update_zones as $id=>$origin) {
184+
$result .= "<h4>".$wb['zone_txt']." $origin</h4>";
185+
186+
$records = $app->db->queryAllRecords('SELECT id, type, name FROM dns_rr WHERE zone = ? AND data = ? AND type IN (\'A\', \'AAAA\') ORDER BY 2,3', $id, $_POST['a_records_search']);
187+
188+
if (count($records) == 0) {
189+
// Zone has no records or no matching records
190+
$result .= $wb['no_matches_txt'];
191+
continue;
192+
}
193+
194+
$result .= "<ul>";
195+
196+
foreach ($records as $record) {
197+
$app->db->datalogUpdate('dns_rr', "data='".$app->db->escape($_POST['a_records_replace'])."'", 'id', $record['id']);
198+
$result .= '<li>'.$record['type']." ".$record['name']." ".htmlentities($_POST['a_records_search']).' &#x27a1; <strong>'.htmlentities($_POST['a_records_replace']).'</strong></li>';
199+
}
200+
201+
$result .= "</ul>";
202+
203+
if (count($records) > 0) {
204+
soa_increase_serial($id);
205+
}
206+
}
207+
208+
break;
209+
case 'mx_records':
210+
$result = '<h3>'.$wb['mx_records_txt'].'</h3>';
211+
212+
foreach ($update_zones as $id=>$origin) {
213+
$result .= "<h4>".$wb['zone_txt']." $origin</h4>";
214+
215+
$search_zone = normalize_zone($_POST['mx_records_search']);
216+
$replace_zone = normalize_zone($_POST['mx_records_replace']);
217+
218+
$records = $app->db->queryAllRecords('SELECT id, type, name FROM dns_rr WHERE zone = ? AND data = ? AND type = \'MX\' ORDER BY 2,3', $id, $search_zone);
219+
220+
if (count($records) == 0) {
221+
// Zone has no records or no matching records
222+
echo 'No matches';
223+
$result .= $wb['no_matches_txt'];
224+
continue;
225+
}
226+
227+
$result .= "<ul>";
228+
229+
foreach ($records as $record) {
230+
$app->db->datalogUpdate('dns_rr', "data='".$app->db->escape($replace_zone)."'", 'id', $record['id']);
231+
$result .= '<li>'.$record['type']." ".$record['name']." ".$search_zone.' &#x27a1; <strong>'.$replace_zone.'</strong></li>';
232+
}
233+
234+
$result .= "</ul>";
235+
236+
if (count($records) > 0) {
237+
soa_increase_serial($id);
238+
}
239+
}
240+
241+
break;
242+
case 'ttl':
243+
$result = '<h3>'.$wb['ttl_txt'].'</h3>';
244+
245+
$ttl = intval($_POST['ttl']);
246+
247+
foreach ($update_zones as $id=>$origin) {
248+
$result .= "<h4>".$wb['zone_txt']." $origin</h4>";
249+
250+
$records = $app->db->queryAllRecords('SELECT id, type, name FROM dns_rr WHERE zone = ? AND type IN (\'A\', \'AAAA\', \'MX\') ORDER BY 2,3', $id);
251+
252+
if (count($records) == 0) {
253+
// Zone has no records?
254+
$result .= $wb['no_matches_txt'];
255+
continue;
256+
}
257+
258+
$result .= "<ul>";
259+
260+
foreach ($records as $record) {
261+
$app->db->datalogUpdate('dns_rr', "ttl=$ttl", 'id', $record['id']);
262+
$result .= "<li>".$record['type']." ".$record['name']." <strong>$ttl</strong></li>";
263+
}
264+
265+
$result .= "</ul>";
266+
267+
if (count($records) > 0) {
268+
soa_increase_serial($id);
269+
}
270+
}
271+
272+
break;
273+
}
274+
}
275+
276+
function validate_ips($search, $replace) {
277+
global $app, $wb;
278+
279+
if (trim($search) == '' || trim($replace) == '') {
280+
$app->tpl->setVar('error', $wb['error_no_search_replace_txt']);
281+
return false;
282+
}
283+
284+
$search_ip_type = get_ip_type($search);
285+
$replace_ip_type = get_ip_type($replace);
286+
287+
if ($search_ip_type == 'none' || $replace_ip_type == 'none') {
288+
$app->tpl->setVar('error', $wb['error_invalid_ip_txt']);
289+
return false;
290+
}
291+
292+
if ($search_ip_type != $replace_ip_type) {
293+
$app->tpl->setVar('error', $wb['error_ip_type_mismatch_txt']);
294+
return false;
295+
}
296+
297+
return true;
298+
}
299+
300+
function get_ip_type($s) {
301+
if (filter_var($s, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) return 'IPv4';
302+
if (filter_var($s, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) return 'IPv6';
303+
return 'none';
304+
}
305+
306+
function validate_zone($zone) {
307+
$s = normalize_zone($zone);
308+
$result = preg_match('/^[a-z0-9\.\-\*]{1,255}$/', $s) === 1;
309+
return $result;
310+
}
311+
312+
function normalize_zone($zone) {
313+
global $app;
314+
315+
$s = trim($zone);
316+
$s = strtolower($s);
317+
$s = $app->functions->idn_encode($s);
318+
return $s;
319+
}
320+
321+
function soa_increase_serial($id) {
322+
global $app;
323+
324+
$soa = $app->db->queryOneRecord('SELECT serial FROM dns_soa WHERE id = ?', $id);
325+
$serial = $app->validate_dns->increase_serial($soa['serial']);
326+
$app->db->datalogUpdate('dns_soa', "serial=$serial", 'id', $id);
327+
}
328+
329+
?>

interface/web/dns/lib/lang/de.lng

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ $wb['Add DNS Zone'] = 'DNS Zone hinzufügen';
1919
$wb['Templates'] = 'Vorlagen';
2020
$wb['Secondary Zones'] = 'Slave DNS-Zonen';
2121
$wb['Import Zone File'] = 'Zonen-Datei-Import';
22-
?>
22+
$wb['Bulk Editor'] = 'Bulk-Editor';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
$wb['list_head_txt'] = 'Bulk-Editor';
3+
$wb['list_desc_txt'] = 'DNS-Einträge per Suchen und Ersetzen ändern';
4+
$wb['error_txt'] = 'Fehler';
5+
$wb['error_no_client_txt'] = 'Bitte einen Kunden auswählen';
6+
$wb['error_no_action_txt'] = 'Bitte eine Aktion auswählen';
7+
$wb['error_no_zone_txt'] = 'Bitte mindestens eine Zone auswählen';
8+
$wb['error_invalid_zone_txt'] = 'Ungültige Zone';
9+
$wb['error_no_search_replace_txt'] = 'Bitte Werte für Suchen und Ersetzen eingeben';
10+
$wb['error_no_ttl_txt'] = 'Bitte gültigen TTL-Wert eingeben';
11+
$wb['error_invalid_ip_txt'] = 'Bitte eine gültige IP-Adresse eingeben';
12+
$wb['error_invalid_dns_zone_txt'] = 'Bitte einen gültigen Zonennamen eingeben';
13+
$wb['error_ip_type_mismatch_txt'] = 'Bitte entweder IPv4- oder IPv6-Adressen eingeben';
14+
$wb['legend_client_txt'] = 'Kunde auswählen';
15+
$wb['legend_action_txt'] = 'Aktion auswählen';
16+
$wb['legend_zones_txt'] = 'Zonen auswählen';
17+
$wb['legend_result_txt'] = 'Folgendes wurde geändert:';
18+
$wb['select_client_txt'] = 'Bitte einen Kunden auswählen';
19+
$wb['client_txt'] = 'Kunde';
20+
$wb['search_txt'] = 'Suchen';
21+
$wb['replace_txt'] = 'Ersetzen';
22+
$wb['a_records_txt'] = 'A-Records';
23+
$wb['mx_records_txt'] = 'MX-Records';
24+
$wb['ttl_txt'] = 'TTL';
25+
$wb['ttl_desc_txt'] = 'von A-, AAAA- und MX-Records';
26+
$wb['check_uncheck_all_txt'] = 'Alle/keine auswählen';
27+
$wb['zone_txt'] = 'Zone';
28+
$wb['btn_ok_txt'] = 'Ändern';
29+
$wb['no_matches_txt'] = 'Keine Treffer.';
30+
$wb['ttip_a_records_txt'] = 'Bitte geben Sie entweder eine vollständige IPv4-Adresse oder eine vollständige IPv6-Adresse in die Felder ein.';
31+
$wb['ttip_mx_records_txt'] = 'Wenn Sie einen kompletten Hostnamen angeben, vergessen Sie bitte auf keinen Fall, einen Punkt am Ende hinzuzufügen, z.B.: www.meinedomain.de<b><u>.</u></b><br><br>Wenn Sie eine Subdomain Ihrer Domain angeben und und die Domain weglassen (z.B. <i>www</i> statt <i>www.meinedomain.de</i>), dann geben Sie bitte <b>keinen</b> Punkt am Ende an.';
32+
$wb['ttip_ttl_txt'] = 'Bitte geben Sie den gewünschten TTL-Wert als Ganzzahl in Sekunden ein.';
33+
?>

interface/web/dns/lib/lang/en.lng

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ $wb['Add DNS Zone'] = 'Add DNS-Zone';
2020
$wb['Templates'] = 'Templates';
2121
$wb['Secondary Zones'] = 'Secondary DNS-Zones';
2222
$wb['Import Zone File'] = 'Zone-File Import';
23-
?>
23+
$wb['Bulk Editor'] = 'Bulk Editor';

0 commit comments

Comments
 (0)