Handle hostnames with upper-case letters
[webmin.git] / dnsadmin / save_record.cgi
1 #!/usr/local/bin/perl
2 # save_record.cgi
3 # Adds or updates a record of some type
4
5 require './dns-lib.pl';
6 &ReadParse();
7 %access = &get_module_acl();
8 &can_edit_zone(\%access, $in{'origin'}) ||
9         &error("You are not allowed to edit records in this zone");
10 &lock_file($in{'file'});
11 @recs = &read_zone_file($in{'file'}, $in{'origin'});
12 $whatfailed = "Failed to save record";
13
14 # get the old record if needed
15 $r = $recs[$in{'num'}] if (defined($in{'num'}));
16
17 # check for deletion
18 if ($in{'delete'}) {
19         &lock_file($r->{'file'});
20         &delete_record($r->{'file'}, $r);
21         &bump_soa_record($in{'file'}, \@recs);
22         ($orevconf, $orevfile, $orevrec) = &find_reverse($in{'oldvalue0'});
23         if ($in{'rev'} && $orevrec && &can_edit_reverse($orevconf) &&
24             $in{'oldname'} eq $orevrec->{'values'}->[0] &&
25             $in{'oldvalue0'} eq &arpa_to_ip($orevrec->{'name'})) {
26                 &lock_file($orevrec->{'file'});
27                 &delete_record($orevrec->{'file'} , $orevrec);
28                 &lock_file($orevfile);
29                 @orrecs = &read_zone_file(
30                                 $orevfile, $orevconf->{'values'}->[0]);
31                 &bump_soa_record($orevfile, \@orrecs);
32                 }
33
34         ($ofwdconf, $ofwdfile, $ofwdrec) = &find_forward($in{'oldvalue0'});
35         if ($in{'fwd'} && $ofwdrec &&
36             &can_edit_zone($ofwdconf->{'values'}->[0]) &&
37             &arpa_to_ip($in{'oldname'}) eq $ofwdrec->{'values'}->[0] &&
38             $in{'oldvalue0'} eq $ofwdrec->{'name'}) {
39                 &lock_file($ofwdrec->{'file'});
40                 &delete_record($ofwdrec->{'file'}, $ofwdrec);
41                 &lock_file($ofwdfile);
42                 @ofrecs = &read_zone_file($ofwdfile,$ofwdconf->{'values'}->[0]);
43                 &bump_soa_record($ofwdfile, \@ofrecs);
44                 }
45
46         &redirect("edit_recs.cgi?index=$in{'index'}&type=$in{'type'}");
47         &unlock_all_files();
48         &webmin_log('delete', 'record', $in{'origin'}, $r);
49         exit;
50         }
51
52 # parse inputs
53 if (!$in{'ttl_def'}) {
54         $in{'ttl'} =~ /^\d+$/ ||
55                 &error("'$in{'ttl'}' is not a valid time-to-live");
56         $ttl = $in{'ttl'};
57         }
58 $vals = $in{'value0'};
59 for($i=1; defined($in{"value$i"}); $i++) {
60         $vals .= " ".$in{"value$i"};
61         }
62 if ($in{'type'} eq "PTR") {
63         # a reverse address
64         &check_ipaddress($in{'name'}) ||
65                 &error("'$in{'name'}' is not a valid IP address");
66         $name = &ip_to_arpa($in{'name'});
67         &valname($in{'value0'}) ||
68                 &error("'$vals[0]' is not a valid hostname");
69         if ($in{'value0'} !~ /\.$/) { $vals .= "."; }
70         }
71 else {
72         # some other kind of record
73         $in{'name'} eq "" || &valname($in{'name'}) ||
74                 &error("'$in{'name'}' is not a valid ",
75                        lc($code_map{$in{'type'}})," record name");
76         if ($in{'type'} eq "A") {
77                 &check_ipaddress($vals) ||
78                         &error("'$vals' is not a valid IP address");
79                 if (!$access{'multiple'}) {
80                         $conf = &get_config();
81                         @zl = &find_config("primary", $conf);
82                         foreach $z (@zl) {
83                                 $file = $z->{'values'}->[1];
84                                 @frecs = &read_zone_file($z->{'values'}->[1],
85                                                          $z->{'values'}->[0]);
86                                 foreach $fr (@frecs) {
87                                         if ($fr->{'type'} eq "A" &&
88                                             $fr->{'values'}->[0] eq $vals &&
89                                             $fr->{'name'} ne $r->{'name'}) {
90                                                 &error("An address record for ",
91                                                        "$vals already exists");
92                                                 }
93                                         }
94                                 }
95                         }
96                 }
97         elsif ($in{'type'} eq "NS") {
98                 &valname($vals) ||
99                         &error("'$vals' is not a valid nameserver");
100                 }
101         elsif ($in{'type'} eq "CNAME") {
102                 &valname($vals) ||
103                         &error("'$vals' is not a valid alias target");
104                 }
105         elsif ($in{'type'} eq "MX") {
106                 $in{'value1'} =~ /^[A-z0-9\-\.\*]+$/ ||
107                         &error("'$in{'value1'}' is not a valid mail server");
108                 $in{'value0'} =~ /^\d+$/ ||
109                         &error("'$in{'value0'}' is not a valid priority");
110                 }
111         elsif ($in{'type'} eq "HINFO") {
112                 $in{'value0'} =~ /^\S+$/ ||
113                         &error("'$in{'value0'}' is not a valid hardware type");
114                 $in{'value1'} =~ /^\S+$/ ||
115                         &error("'$in{'value1'}' is not a valid OS type");
116                 }
117         elsif ($in{'type'} eq "TXT") {
118                 $vals = "\"$in{'value0'}\"";
119                 }
120         elsif ($in{'type'} eq "WKS") {
121                 &check_ipaddress($in{'value0'}) ||
122                         &error("'$in{'value0'}' is not a valid IP address");
123                 if (!$in{'value2'}) {
124                         &error("You did not enter any well known services");
125                         }
126                 @ws = split(/[\r\n]+/, $in{'value2'});
127                 $vals = "$in{'value0'} $in{'value1'} (";
128                 foreach $ws (@ws) { $vals .= "\n\t\t\t\t\t$ws"; }
129                 $vals .= " )";
130                 }
131         elsif ($in{'type'} eq "RP") {
132                 $in{'value0'} =~ /^(\S+)\@(\S+)$/ ||
133                         &error("'$in{'value0'}' is not a valid email address");
134                 &valname($in{'value1'}) ||
135                         &error("'$in{'value1'}' is not a valid text record");
136                 $in{'value0'} =~ s/\@/\./g;
137                 $vals = "$in{'value0'} $in{'value1'}";
138                 }
139         $name = $in{'name'} eq "" ?    "$in{'origin'}." :
140                 $in{'name'} !~ /\.$/ ? "$in{'name'}.$in{'origin'}." :
141                                        $in{'name'};
142         }
143
144 if ($in{'new'}) {
145         # just adding a new record
146         &create_record($in{'file'}, $name, $ttl, "IN", $in{'type'}, $vals);
147         $r = { 'name' => $name, 'ttl' => $ttl, 'class' => 'IN',
148                'type' => $in{'type'}, 'values' => [ split(/\s+/, $vals) ] };
149         ($revconf, $revfile, $revrec) = &find_reverse($in{'value0'});
150         if ($in{'rev'} && $revconf && !$revrec && &can_edit_reverse($revconf)) {
151                 # Add a reverse record if we are the master for the reverse
152                 # domain, and if there is not already a reverse record
153                 # for the address.
154                 &lock_file($revfile);
155                 &create_record($revfile,
156                               &ip_to_arpa($in{'value0'}), $ttl,
157                               "IN", "PTR", $name);
158                 @rrecs = &read_zone_file($revfile, $revconf->{'values'}->[0]);
159                 &bump_soa_record($revfile, \@rrecs);
160                 }
161
162         ($fwdconf, $fwdfile, $fwdrec) = &find_forward($vals);
163         if ($in{'fwd'} && $fwdconf && !$fwdrec &&
164             &can_edit_zone($fwdconf->{'values'}->[0])) {
165                 # Add a forward record if we are the master for the forward
166                 # domain, and if there is not already an A record
167                 # for the address
168                 &lock_file($fwdfile);
169                 &create_record($fwdfile, $vals,
170                                $ttl, "IN", "A", $in{'name'});
171                 @frecs = &read_zone_file($fwdfile, $fwdconf->{'values'}->[0]);
172                 &bump_soa_record($fwdfile, \@frecs);
173                 }
174         }
175 else {
176         # updating an existing record
177         ($orevconf, $orevfile, $orevrec) = &find_reverse($in{'oldvalue0'});
178         ($revconf, $revfile, $revrec) = &find_reverse($in{'value0'});
179         &lock_file($r->{'file'});
180         &modify_record($r->{'file'}, $r, $name, $ttl,
181                        "IN", $in{'type'},$vals);
182
183         if ($in{'rev'} && $orevrec && &can_edit_reverse($orevconf) &&
184             $in{'oldname'} eq $orevrec->{'values'}->[0] &&
185             $in{'oldvalue0'} eq &arpa_to_ip($orevrec->{'name'})) {
186                 # Updating the reverse record. Either the name, address
187                 # or both may have changed. Furthermore, the reverse record
188                 # may now be in a different file!
189                 &lock_file($orevfile);
190                 &lock_file($revfile);
191                 @orrecs = &read_zone_file($orevfile,$orevconf->{'values'}->[0]);
192                 @rrecs = &read_zone_file($revfile, $revconf->{'values'}->[0]);
193                 if ($revconf eq $orevconf && &can_edit_reverse($revconf)) {
194                         # old and new in the same file
195                         &modify_record($orevrec->{'file'} , $orevrec, 
196                                       &ip_to_arpa($in{'value0'}),
197                                       $orevrec->{'ttl'}, "IN", "PTR", $name);
198                         &bump_soa_record($orevfile, \@orrecs);
199                         }
200                 elsif ($revconf && &can_edit_reverse($revconf)) {
201                         # old and new in different files
202                         &delete_record($orevrec->{'file'} , $orevrec);
203                         &create_record($revfile, &ip_to_arpa($in{'value0'}),
204                                       $orevrec->{'ttl'}, "IN", "PTR", $name);
205                         &bump_soa_record($orevfile, \@orrecs);
206                         &bump_soa_record($revfile, \@rrecs);
207                         }
208                 else {
209                         # we don't handle the new reverse domain.. lose the
210                         # reverse record
211                         &delete_record($orevrec->{'file'}, $orevrec);
212                         &bump_soa_record($orevfile, \@orrecs);
213                         }
214                 }
215
216         ($ofwdconf, $ofwdfile, $ofwdrec) = &find_forward($in{'oldvalue0'});
217         ($fwdconf, $fwdfile, $fwdrec) = &find_forward($in{'value0'});
218         if ($in{'fwd'} && $ofwdrec &&
219             &can_edit_zone($ofwdconf->{'values'}->[0]) &&
220             &arpa_to_ip($in{'oldname'}) eq $ofwdrec->{'values'}->[0] &&
221             $in{'oldvalue0'} eq $ofwdrec->{'name'}) {
222                 # Updating the forward record
223                 &lock_file($ofwdfile);
224                 &lock_file($fwdfile);
225                 @ofrecs = &read_zone_file($ofwdfile,$ofwdconf->{'values'}->[0]);
226                 @frecs = &read_zone_file($fwdfile, $fwdconf->{'values'}->[0]);
227                 if ($fwdconf eq $ofwdconf &&
228                     &can_edit_zone($fwdconf->{'values'}->[0])) {
229                         # old and new are in the same file
230                         &modify_record($ofwdrec->{'file'} , $ofwdrec, $vals,
231                                        $ofwdrec->{'ttl'}, "IN", "A",
232                                        $in{'name'});
233                         &bump_soa_record($ofwdfile, \@ofrecs);
234                         }
235                 elsif ($fwdconf && &can_edit_zone($fwdconf->{'values'}->[0])) {
236                         # old and new in different files
237                         &delete_record($ofwdrec->{'file'} , $ofwdrec);
238                         &create_record($fwdfile, $vals, $ofwdrec->{'ttl'},
239                                        "IN", "A", $in{'name'});
240                         &bump_soa_record($ofwdfile, \@ofrecs);
241                         &bump_soa_record($fwdfile, \@frecs);
242                         }
243                 else {
244                         # lose the forward because it has been moved to
245                         # a zone not handled by this server
246                         &delete_record($ofwdrec->{'file'} , $ofwdrec);
247                         &bump_soa_record($ofwdfile, \@ofrecs);
248                         }
249                 }
250         }
251 &bump_soa_record($in{'file'}, \@recs);
252 &unlock_all_files();
253 &webmin_log($in{'new'} ? 'create' : 'modify', 'record', $in{'origin'}, $r);
254 &redirect("edit_recs.cgi?index=$in{'index'}&type=$in{'type'}");
255
256 sub valname
257 {
258 return $_[0] =~ /[A-z0-9\-\.]+$/;
259 }
260
261 # can_edit_reverse(&zone)
262 sub can_edit_reverse
263 {
264 return $access{'reverse'} || &can_edit_zone(\%access, $_[0]->{'values'}->[0]);
265 }
266