3 # Adds or updates a record of some type
5 require './bind8-lib.pl';
7 &error_setup($text{'edit_err'});
8 $zone = &get_zone_name($in{'index'}, $in{'view'});
9 $dom = $zone->{'name'};
10 @zl = &list_zone_names();
11 $reverse = ($in{'origin'} =~ /\.in-addr\.arpa/i ||
12 $in{'origin'} =~ /\.$ipv6revzone/i);
13 &can_edit_zone($zone) || &error($text{'recs_ecannot'});
14 &can_edit_type($in{'type'}) ||
15 &error($text{'recs_ecannottype'});
16 $access{'ro'} && &error($text{'master_ero'});
17 &lock_file(&make_chroot(&absolute_path($zone->{'file'})));
19 # Read the existing records
20 if ($config{'largezones'} && !defined($in{'num'})) {
21 # Adding to a large zone, so only read the SOA
22 @recs = &read_zone_file($in{'file'}, $in{'origin'}, undef, 1);
26 @recs = &read_zone_file($in{'file'}, $in{'origin'});
29 # get the old record if needed
30 if (defined($in{'num'})) {
31 $r = &find_record_by_id(\@recs, $in{'id'}, $in{'num'});
32 $r || &error($text{'edit_egone'});
37 # Check if confirmation is needed
38 if (!$in{'confirm'} && $config{'confirm_rec'}) {
39 &ui_print_header(undef, $text{'edit_dtitle'}, "");
41 print &ui_confirmation_form("save_record.cgi",
42 &text('edit_rusure', "<tt>$r->{'name'}</tt>",
43 "<tt>$in{'origin'}</tt>"),
44 [ map { [ $_, $in{$_} ] } (keys %in) ],
45 [ [ 'confirm', $text{'edit_dok'} ] ],
48 &ui_print_footer("edit_recs.cgi?index=$in{'index'}&view=$in{'view'}&type=$in{'redirtype'}&sort=$in{'sort'}", $text{'recs_return'});
52 &lock_file(&make_chroot($r->{'file'}));
53 &delete_record($r->{'file'}, $r);
54 &bump_soa_record($in{'file'}, \@recs);
55 &sign_dnssec_zone_if_key($zone, \@recs);
58 $fulloldvalue0 = &convert_to_absolute(
59 $in{'oldvalue0'}, $in{'origin'});
60 $fulloldname = &convert_to_absolute(
61 $in{'oldname'}, $in{'origin'});
62 ($orevconf, $orevfile, $orevrec) = &find_reverse($in{'oldvalue0'},
64 if ($in{'rev'} && $orevrec && &can_edit_reverse($orevconf) &&
65 $fulloldname eq $orevrec->{'values'}->[0] &&
66 ($in{'type'} eq "A" &&
67 $in{'oldvalue0'} eq &arpa_to_ip($orevrec->{'name'}) ||
68 $in{'type'} eq "AAAA" &&
69 &expandall_ip6($in{'oldvalue0'}) eq &expandall_ip6(&ip6int_to_net($orevrec->{'name'})))) {
70 &lock_file(&make_chroot($orevrec->{'file'}));
71 &delete_record($orevrec->{'file'} , $orevrec);
72 &lock_file(&make_chroot($orevfile));
73 @orrecs = &read_zone_file($orevfile, $orevconf->{'name'});
74 &bump_soa_record($orevfile, \@orrecs);
75 &sign_dnssec_zone_if_key($orevconf, \@orrecs);
79 local($ipv6 = ($fulloldvalue0 =~ /\.$ipv6revzone/i));
80 ($ofwdconf, $ofwdfile, $ofwdrec) = &find_forward($fulloldvalue0, $ipv6);
81 if ($in{'fwd'} && $ofwdrec && &can_edit_zone($ofwdconf) &&
82 (!$ipv6 && &arpa_to_ip($in{'oldname'}) eq $ofwdrec->{'values'}->[0] ||
83 $ipv6 && &expandall_ip6(&ip6int_to_net($in{'oldname'})) eq &expandall_ip6($ofwdrec->{'values'}->[0])) &&
84 $fulloldvalue0 eq $ofwdrec->{'name'}) {
85 &lock_file(&make_chroot($ofwdrec->{'file'}));
86 &delete_record($ofwdrec->{'file'}, $ofwdrec);
87 &lock_file(&make_chroot($ofwdfile));
88 @ofrecs = &read_zone_file($ofwdfile, $ofwdconf->{'name'});
89 &bump_soa_record($ofwdfile, \@ofrecs);
90 &sign_dnssec_zone_if_key($ofwdconf, \@ofrecs);
93 &redirect("edit_recs.cgi?index=$in{'index'}&view=$in{'view'}&type=$in{'redirtype'}&sort=$in{'sort'}");
95 &webmin_log('delete', 'record', $in{'origin'}, $r);
100 # Create values string based on inputs
101 if (!$in{'ttl_def'}) {
102 $in{'ttl'} =~ /^\d+$/ ||
103 &error(&text('edit_ettl', $in{'ttl'}));
104 $ttl = $in{'ttl'}.$in{'ttlunit'};
106 $vals = $in{'value0'};
107 for($i=1; defined($in{"value$i"}); $i++) {
108 $vals .= " ".$in{"value$i"};
113 if ($in{'type'} eq "PTR" && $reverse) {
116 ($ipv4 = $in{'origin'} =~ /in-addr\.arpa/i) ||
117 $in{'origin'} =~ /\.$ipv6revzone/i ||
118 &error(&text('edit_eip', $in{'name'}));
120 if ($in{'name'} =~ /^\d+$/) {
121 $in{'name'} = &arpa_to_ip($in{'origin'}).".".$in{'name'};
123 &check_ipaddress($in{'name'}) ||
124 ($in{'name'} =~ /^(.*)\.(\d+)$/ && &check_ipaddress("$1")) ||
125 ($in{'name'} =~ /^(.*)\.(\d+)$/ && $1 eq &arpa_to_ip($in{'origin'})) ||
126 &error(&text('edit_eip', $in{'name'}));
127 $name = &ip_to_arpa($in{'name'});
130 &check_ip6address($in{'name'}) ||
131 &error(&text('edit_eip6', $in{'name'}));
132 $name = &net_to_ip6int($in{'name'});
134 &valname($in{'value0'}) ||
135 &error(&text('edit_ehost', $vals[0]));
136 if ($in{'value0'} !~ /\.$/) { $vals .= "."; }
139 # some other kind of record
140 $in{'name'} eq "" || $in{'name'} eq "@" || &valnamewild($in{'name'}) ||
141 &error(&text('edit_ename', $in{'name'}));
142 if ($in{'type'} eq "A") {
143 &check_ipaddress($vals) ||
144 &error(&text('edit_eip', $vals));
145 if (!$access{'multiple'}) {
146 # Is this address already in use? Search all domains
149 next if ($z->{'type'} ne "master");
150 next if ($z->{'name'} =~ /in-addr\.arpa/i);
151 $file = $z->{'file'};
152 @frecs = &read_zone_file($file, $z->{'name'});
153 foreach $fr (@frecs) {
154 if ($fr->{'type'} eq "A" &&
155 $fr->{'values'}->[0] eq $vals &&
156 $fr->{'name'} ne $r->{'name'}) {
157 &error(&text('edit_edupip',
164 elsif ($in{'type'} eq "AAAA") {
165 &check_ip6address($vals) ||
166 &error(&text('edit_eip6', $vals));
167 if (!$access{'multiple'}) {
168 # Is this address already in use? Search all domains
171 next if ($z->{'type'} ne "master");
172 next if ($z->{'name'} =~ /\.$ipv6revzone/i);
173 $file = $z->{'file'};
174 @frecs = &read_zone_file($file, $z->{'name'});
175 foreach $fr (@frecs) {
176 if ($fr->{'type'} eq "AAAA" &&
177 &expandall_ip6($fr->{'values'}->[0]) eq &expandall_ip6($vals) &&
178 $fr->{'name'} ne $r->{'name'}) {
179 &error(&text('edit_edupip',
186 elsif ($in{'type'} eq "NS") {
188 &error(&text('edit_ens', $vals));
189 if ($vals =~ /\.\Q$in{'origin'}\E$/) {
194 elsif ($in{'type'} eq "CNAME") {
195 &valname($vals) || $vals eq '@' ||
196 &error(&text('edit_ecname', $vals));
197 if ($vals =~ /\.\Q$in{'origin'}\E$/) {
201 elsif ($in{'type'} eq "MX") {
202 &valname($in{'value1'}) ||
203 &error(&text('edit_emx', $in{'value1'}));
204 $in{'value0'} =~ /^\d+$/ ||
205 &error(&text('edit_epri', $in{'value0'}));
206 if ($vals =~ /\.\Q$in{'origin'}\E$/) {
210 elsif ($in{'type'} eq "HINFO") {
211 $in{'value0'} =~ /\S/ ||
212 &error($text{'edit_ehard'});
213 $in{'value1'} =~ /\S/ ||
214 &error($text{'edit_eos'});
215 $in{'value0'} = "\"$in{'value0'}\"" if ($in{'value0'} =~ /\s/);
216 $in{'value1'} = "\"$in{'value1'}\"" if ($in{'value1'} =~ /\s/);
217 $vals = $in{'value0'}." ".$in{'value1'};
219 elsif ($in{'type'} eq "TXT") {
220 $vals = $in{'value0'};
221 $vals =~ s/((?:^|[^\\])(?:\\\\)*)[\"]/$1\\\"/g;
224 elsif ($in{'type'} eq "WKS") {
225 &check_ipaddress($in{'value0'}) ||
226 &error(&text('edit_eip', $in{'value0'}));
227 if (!$in{'value2'}) {
228 &error($text{'edit_eserv'});
230 @ws = split(/[\r\n]+|\s+/, $in{'value2'});
231 $vals = "$in{'value0'} $in{'value1'} (";
233 $ws =~ /^[a-z]([\w\-]*\w)?$/i ||
234 &error(&text('edit_ebadserv', $ws));
235 $vals .= "\n\t\t\t\t\t$ws";
239 elsif ($in{'type'} eq "RP") {
240 if (!$in{'value0'}) {
243 elsif (!&valemail($in{'value0'})) {
244 &error(&text('edit_eemail', $in{'value0'}));
246 &valname($in{'value1'}) ||
247 &error(&text('edit_etxt', $in{'value1'}));
248 $in{'value0'} = &email_to_dotted($in{'value0'});
249 $vals = "$in{'value0'} $in{'value1'}";
251 elsif ($in{'type'} eq "LOC") {
252 $in{'value0'} =~ /\S/ || &error($text{'edit_eloc'});
254 elsif ($in{'type'} eq 'SRV') {
255 $in{'serv'} =~ /^[A-Za-z0-9\-\_]+$/ ||
256 &error(&text('edit_eserv2', $in{'serv'}));
257 $in{'name'} = join(".", "_".$in{'serv'}, "_".$in{'proto'},
258 $in{'name'} ? ( $in{'name'} ) : ( ));
259 $in{'value0'} =~ /^\d+$/ ||
260 &error(text('edit_epri', $in{'value0'}));
261 $in{'value1'} =~ /^\d+$/ ||
262 &error(text('edit_eweight', $in{'value1'}));
263 $in{'value2'} =~ /^\d+$/ ||
264 &error(text('edit_eport', $in{'value2'}));
265 &valname($in{'value3'}) ||
266 &error(&text('edit_etarget', $in{'value3'}));
268 elsif ($in{'type'} eq 'KEY') {
269 $in{'value0'} =~ /^(\d+|0x[0-9a-f]+)$/i ||
270 &error(text('edit_eflags', $in{'value0'}));
271 $in{'value1'} =~ /^\d+$/ ||
272 &error(text('edit_eproto', $in{'value1'}));
273 $in{'value2'} =~ /^\d+$/ ||
274 &error(text('edit_ealg', $in{'value2'}));
275 $in{'value3'} =~ s/[ \r\n]//g;
276 $in{'value3'} =~ /^[a-zA-Z0-9\/\+]+$/ ||
277 &error(text('edit_ekey'));
278 $vals = join(" ", $in{'value0'}, $in{'value1'},
279 $in{'value2'}, $in{'value3'});
281 elsif ($in{'type'} eq 'PTR') {
282 $vals = $in{'value0'};
284 &error(&text('edit_eptr', $vals));
286 elsif ($in{'type'} eq 'SPF') {
287 # For SPF records, build the SPF string from the inputs
288 $spf = $r ? &parse_spf(@{$r->{'values'}}) : { };
289 $spf->{'a'} = $in{'spfa'};
290 $spf->{'mx'} = $in{'spfmx'};
291 $spf->{'ptr'} = $in{'spfptr'};
292 $spf->{'a:'} = [ split(/\s+/, $in{'spfas'}) ];
293 foreach my $a (@{$spf->{'a:'}}) {
294 &to_ipaddress($a) || &error(&text('edit_espfa', $a));
296 $spf->{'mx:'} = [ split(/\s+/, $in{'spfmxs'}) ];
297 foreach my $mx (@{$spf->{'mx:'}}) {
298 &valname($mx) || &error(&text('edit_espfmx', $mx));
300 @{$spf->{'mx:'}} <= 10 ||
301 &error(&text('edit_espfmxmax', 10));
302 $spf->{'ip4:'} = [ split(/\s+/, $in{'spfip4s'}) ];
303 foreach my $ip (@{$spf->{'ip4:'}}) {
304 &check_ipaddress($ip) ||
305 ($ip =~ /^(\S+)\/\d+$/ && &check_ipaddress($1)) ||
306 &error(&text('edit_espfip', $ip));
308 if (&supports_ipv6()) {
309 $spf->{'ip6:'} = [ split(/\s+/, $in{'spfip6s'}) ];
310 foreach my $ip (@{$spf->{'ip6:'}}) {
311 &check_ip6address($ip) ||
312 ($ip =~ /^(\S+)\/\d+$/ &&
313 &check_ip6address($1)) ||
314 &error(&text('edit_espfip6', $ip));
317 $spf->{'include:'} = [ split(/\s+/, $in{'spfincludes'}) ];
318 foreach my $i (@{$spf->{'include:'}}) {
319 &valname($i) || &error(&text('edit_espfinclude', $i));
321 $spf->{'all'} = $in{'spfall'};
322 foreach my $m ('redirect', 'exp') {
323 if ($in{'spf'.$m.'_def'}) {
327 &valname($in{'spf'.$m}) ||
328 &error(&text('edit_espf'.$m,
330 $spf->{$m} = $in{'spf'.$m};
333 $vals = "\"".&join_spf($spf)."\"";
336 # For other record types, just save the lines
337 $in{'values'} =~ s/\r//g;
338 local @vlines = split(/\n/, $in{'values'});
339 $vals = join(" ",map { $_ =~ /^\S+$/ ? $_ : "\"$_\"" } @vlines);
341 $fullname = &convert_to_absolute($in{'name'}, $in{'origin'});
342 if ($config{'short_names'}) {
350 # check for CNAME collision
351 if (!defined($in{'num'}) || $name ne $r->{'name'}) {
352 foreach $cr (@recs) {
353 if ($cr->{'name'} eq $name) {
354 if ($in{'type'} eq CNAME) {
355 &error($text{'edit_ecname1'});
357 elsif ($cr->{'type'} eq 'CNAME') {
358 &error($text{'edit_ecname2'});
365 # adding a new record
366 ($revconf, $revfile, $revrec) = &find_reverse($in{'value0'},
368 if ($in{'rev'} && $config{'rev_must'} && !$revconf) {
369 # Reverse zone must exist, but doesn't
370 &error($text{'edit_erevmust'});
372 &create_record($in{'file'}, $name, $ttl, "IN", $in{'type'}, $vals,
374 $r = { 'name' => $name, 'ttl' => $ttl, 'class' => 'IN',
375 'type' => $in{'type'}, 'values' => [ split(/\s+/, $vals) ],
376 'comment' => $in{'comment'} };
377 if ($in{'rev'} && $revconf && &can_edit_reverse($revconf) &&
378 $in{'value0'} !~ /\*/) {
379 local $rname = $in{'type'} eq "A" ? &ip_to_arpa($in{'value0'})
380 : &net_to_ip6int($in{'value0'});
381 if ($revrec && $in{'rev'} == 2) {
382 # Upate the existing reverse for the domain
383 &lock_file(&make_chroot($revrec->{'file'}));
384 &modify_record($revrec->{'file'}, $revrec,
385 $rname, $revrec->{'ttl'}, "IN", "PTR",
387 @rrecs = &read_zone_file($revfile, $revconf->{'name'});
388 &bump_soa_record($revfile, \@rrecs);
389 &sign_dnssec_zone_if_key($revconf, \@rrecs);
392 # Add a reverse record if we are the master for the
393 # reverse domain, and if there is not already a
394 # reverse record for the address.
395 &lock_file(&make_chroot($revfile));
396 &create_record($revfile, $rname,
397 $ttl, "IN", "PTR", $fullname);
398 @rrecs = &read_zone_file($revfile, $revconf->{'name'});
399 &bump_soa_record($revfile, \@rrecs);
400 &sign_dnssec_zone_if_key($revconf, \@rrecs);
404 ($fwdconf, $fwdfile, $fwdrec) = &find_forward($vals, $vals =~ /\.$ipv6revzone/i);
405 if ($in{'fwd'} && $fwdconf && !$fwdrec &&
406 &can_edit_zone($fwdconf)) {
407 # Add a forward record if we are the master for the forward
408 # domain, and if there is not already an A record
411 if (&check_ipaddress($in{'name'})) {
414 elsif ($config{'support_aaaa'} &&
415 &check_ip6address($in{'name'})) {
419 &lock_file(&make_chroot($fwdfile));
420 &create_record($fwdfile, $vals,
421 $ttl, "IN", $rtype, $in{'name'});
422 @frecs = &read_zone_file($fwdfile, $fwdconf->{'name'});
423 &bump_soa_record($fwdfile, \@frecs);
424 &sign_dnssec_zone_if_key($fwdconf, \@frecs);
429 # update an existing record
430 $fulloldvalue0 = &convert_to_absolute($in{'oldvalue0'}, $in{'origin'});
431 $fulloldname = &convert_to_absolute($in{'oldname'}, $in{'origin'});
432 ($orevconf, $orevfile, $orevrec) = &find_reverse($in{'oldvalue0'},
434 ($revconf, $revfile, $revrec) = &find_reverse($in{'value0'},
436 if ($in{'rev'} && $config{'rev_must'} && !$revconf) {
437 # Reverse zone must exist, but doesn't
438 &error($text{'edit_erevmust'});
440 &lock_file(&make_chroot($r->{'file'}));
441 &modify_record($r->{'file'}, $r, $name, $ttl,
442 "IN", $in{'type'}, $vals, $in{'comment'});
444 if ($in{'rev'} && $orevrec && &can_edit_reverse($orevconf) &&
445 $fulloldname eq $orevrec->{'values'}->[0] &&
446 ($in{'type'} eq "A" &&
447 $in{'oldvalue0'} eq &arpa_to_ip($orevrec->{'name'}) ||
448 $in{'type'} eq "AAAA" &&
449 &expandall_ip6($in{'oldvalue0'}) eq &expandall_ip6(&ip6int_to_net($orevrec->{'name'})))) {
450 # Updating the reverse record. Either the name, address
451 # or both may have changed. Furthermore, the reverse record
452 # may now be in a different file!
453 &lock_file(&make_chroot($orevfile));
454 &lock_file(&make_chroot($revfile));
455 @orrecs = &read_zone_file($orevfile, $orevconf->{'name'});
456 @rrecs = &read_zone_file($revfile, $revconf->{'name'});
457 if ($revconf eq $orevconf && &can_edit_reverse($revconf)) {
458 # old and new in the same file
459 &modify_record($orevrec->{'file'} , $orevrec,
460 &net_to_ip6int(&ip_to_arpa($in{'value0'})),
461 $orevrec->{'ttl'}, "IN", "PTR", $fullname,
463 &bump_soa_record($orevfile, \@orrecs);
464 &sign_dnssec_zone_if_key($orevconf, \@orrecs);
466 elsif ($revconf && &can_edit_reverse($revconf)) {
467 # old and new in different files
468 &delete_record($orevrec->{'file'} , $orevrec);
469 &create_record($revfile, &net_to_ip6int(&ip_to_arpa($in{'value0'})),
470 $orevrec->{'ttl'}, "IN", "PTR", $fullname,
472 &bump_soa_record($orevfile, \@orrecs);
473 &bump_soa_record($revfile, \@rrecs);
474 &sign_dnssec_zone_if_key($orevconf, \@orrecs);
475 &sign_dnssec_zone_if_key($revconf, \@rrecs);
478 # we don't handle the new reverse domain.. lose the
480 &delete_record($orevrec->{'file'}, $orevrec);
481 &bump_soa_record($orevfile, \@orrecs);
482 &sign_dnssec_zone_if_key($orevconf, \@orrecs);
485 elsif ($in{'rev'} && !$orevrec && $revconf && !$revrec &&
486 &can_edit_reverse($revconf)) {
487 # we don't handle the old reverse domain but handle the new
488 # one.. create a new reverse record
489 &lock_file(&make_chroot($revfile));
490 @rrecs = &read_zone_file($revfile, $revconf->{'name'});
491 &create_record($revfile, &net_to_ip6int(&ip_to_arpa($in{'value0'})),
492 $ttl, "IN", "PTR", $fullname, $in{'comment'});
493 &bump_soa_record($revfile, \@rrecs);
494 &sign_dnssec_zone_if_key($revconf, \@rrecs);
497 local($ipv6 = ($in{'value0'} =~ /\.$ipv6revzone/i));
498 ($ofwdconf, $ofwdfile, $ofwdrec) = &find_forward($fulloldvalue0, $ipv6);
499 ($fwdconf, $fwdfile, $fwdrec) = &find_forward($in{'value0'}, $ipv6);
500 if ($in{'fwd'} && $ofwdrec && &can_edit_zone($ofwdconf) &&
501 &expandall_ip6(&ip6int_to_net(&arpa_to_ip($in{'oldname'}))) eq
502 &expandall_ip6($ofwdrec->{'values'}->[0]) &&
503 $fulloldvalue0 eq $ofwdrec->{'name'}) {
504 # Updating the forward record
505 &lock_file(&make_chroot($ofwdfile));
506 &lock_file(&make_chroot($fwdfile));
507 @ofrecs = &read_zone_file($ofwdfile, $ofwdconf->{'name'});
508 @frecs = &read_zone_file($fwdfile, $fwdconf->{'name'});
509 if ($fwdconf eq $ofwdconf &&
510 &can_edit_zone($fwdconf)) {
511 # old and new are in the same file
512 &modify_record($ofwdrec->{'file'} , $ofwdrec, $vals,
513 $ofwdrec->{'ttl'}, "IN",
514 $ipv6 ? "AAAA" : "A",
515 $in{'name'}, $in{'comment'});
516 &bump_soa_record($ofwdfile, \@ofrecs);
517 &sign_dnssec_zone_if_key($ofwdconf, \@ofrecs);
519 elsif ($fwdconf && &can_edit_zone($fwdconf)) {
520 # old and new in different files
521 &delete_record($ofwdrec->{'file'} , $ofwdrec);
522 if (!ipv6 || $config{'support_aaaa'}) {
523 &create_record($fwdfile, $vals, $ofwdrec->{'ttl'},
524 "IN", $ipv6 ? "AAAA" : "A",
525 $in{'name'}, $in{'comment'});
526 &bump_soa_record($fwdfile, \@frecs);
527 &sign_dnssec_zone_if_key($fwdconf, \@frecs);
529 &bump_soa_record($ofwdfile, \@ofrecs);
530 &sign_dnssec_zone_if_key($ofwdconf, \@ofrecs);
533 # lose the forward because it has been moved to
534 # a zone not handled by this server
535 &delete_record($ofwdrec->{'file'} , $ofwdrec);
536 &bump_soa_record($ofwdfile, \@ofrecs);
537 &sign_dnssec_zone_if_key($ofwdconf, \@ofrecs);
541 &bump_soa_record($in{'file'}, \@recs);
542 &sign_dnssec_zone_if_key($zone, \@recs);
544 $r->{'newvalues'} = $vals;
545 &webmin_log($in{'new'} ? 'create' : 'modify', 'record', $in{'origin'}, $r);
546 &redirect("edit_recs.cgi?index=$in{'index'}&view=$in{'view'}&".
547 "type=$in{'redirtype'}&sort=$in{'sort'}");
552 return valdnsname($_[0], 0, $in{'origin'});
558 return valdnsname($_[0], 1, $in{'origin'});