]> git.ipfire.org Git - ipfire-2.x.git/blob - html/cgi-bin/dhcp.cgi
DHCP: Allow using external name servers for leases
[ipfire-2.x.git] / html / cgi-bin / dhcp.cgi
1 #!/usr/bin/perl
2 ###############################################################################
3 # #
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2007 Michael Tremer & Christian Schmidt #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 use strict;
23
24 # enable only the following on debugging purpose
25 #use warnings;
26 #use CGI::Carp 'fatalsToBrowser';
27
28 require '/var/ipfire/general-functions.pl';
29 require "${General::swroot}/lang.pl";
30 require "${General::swroot}/header.pl";
31 #workaround to suppress a warning when a variable is used only once
32 my @dummy = ( ${Header::colouryellow} );
33 undef (@dummy);
34
35 our %dhcpsettings=();
36 our %netsettings=();
37 my %mainsettings=();
38 my %timesettings=();
39 my $setting = "${General::swroot}/dhcp/settings";
40 our $filename1 = "${General::swroot}/dhcp/advoptions"; # Field separator is TAB in this file (comma is standart)
41 # because we need commas in the some data
42 our $filename2 = "${General::swroot}/dhcp/fixleases";
43 our $filename3 = "${General::swroot}/dhcp/advoptions-list"; # Describe the allowed syntax for dhcp options
44 my $errormessage = '';
45 my $warnNTPmessage = '';
46 my @nosaved=();
47 my %color = ();
48
49 #Basic syntax allowed for new Option definition. Not implemented: RECORDS & array of RECORDS
50 our $OptionTypes = 'boolean|((un)?signed )?integer (8|16|32)|ip-address|text|string|encapsulate \w+|array of ip-address';
51
52 &Header::showhttpheaders();
53 our @ITFs=('GREEN');
54 if (&Header::blue_used()){push(@ITFs,'BLUE');}
55
56 #Settings1 for the first screen box
57 foreach my $itf (@ITFs) {
58 $dhcpsettings{"ENABLE_${itf}"} = 'off';
59 $dhcpsettings{"ENABLEBOOTP_${itf}"} = 'off';
60 $dhcpsettings{"START_ADDR_${itf}"} = '';
61 $dhcpsettings{"END_ADDR_${itf}"} = '';
62 $dhcpsettings{"DOMAIN_NAME_${itf}"} = '';
63 $dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} = '';
64 $dhcpsettings{"MAX_LEASE_TIME_${itf}"} = '';
65 $dhcpsettings{"WINS1_${itf}"} = '';
66 $dhcpsettings{"WINS2_${itf}"} = '';
67 $dhcpsettings{"DNS1_${itf}"} = '';
68 $dhcpsettings{"DNS2_${itf}"} = '';
69 $dhcpsettings{"NTP1_${itf}"} = '';
70 $dhcpsettings{"NTP2_${itf}"} = '';
71 $dhcpsettings{"NEXT_${itf}"} = '';
72 $dhcpsettings{"FILE_${itf}"} = '';
73 $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} = '';
74 $dhcpsettings{"DNS_UPDATE_KEY_SECRET_${itf}"} = '';
75 $dhcpsettings{"DNS_UPDATE_KEY_ALGO_${itf}"} = '';
76 }
77
78 $dhcpsettings{'SORT_FLEASELIST'} = 'FIPADDR';
79 $dhcpsettings{'SORT_LEASELIST'} = 'IPADDR';
80
81 # DNS Update settings
82 $dhcpsettings{'DNS_UPDATE_ENABLED'} = 'off';
83
84 #Settings2 for editing the multi-line list
85 #Must not be saved with writehash !
86 $dhcpsettings{'FIX_MAC'} = '';
87 $dhcpsettings{'FIX_ADDR'} = '';
88 $dhcpsettings{'FIX_ENABLED'} = 'off';
89 $dhcpsettings{'FIX_NEXTADDR'} = '';
90 $dhcpsettings{'FIX_FILENAME'} = '';
91 $dhcpsettings{'FIX_ROOTPATH'} = '';
92 $dhcpsettings{'FIX_REMARK'} = '';
93 $dhcpsettings{'ACTION'} = '';
94 $dhcpsettings{'KEY1'} = '';
95 $dhcpsettings{'KEY2'} = '';
96 @nosaved=('FIX_MAC','FIX_ADDR','FIX_ENABLED','FIX_NEXTADDR',
97 'FIX_FILENAME','FIX_ROOTPATH','FIX_REMARK');
98
99 $dhcpsettings{'ADVOPT_ENABLED'} = '';
100 $dhcpsettings{'ADVOPT_NAME'} = '';
101 $dhcpsettings{'ADVOPT_DATA'} = '';
102 unshift (@nosaved,'ADVOPT_ENABLED','ADVOPT_NAME','ADVOPT_DATA');
103 foreach my $itf (@ITFs) {
104 $dhcpsettings{"ADVOPT_SCOPE_${itf}"} = 'off';
105 unshift (@nosaved, "ADVOPT_SCOPE_${itf}");
106 }
107
108 # Read Ipcop settings
109 &General::readhash("${General::swroot}/ethernet/settings", \%netsettings);
110 &General::readhash("${General::swroot}/main/settings", \%mainsettings);
111 &General::readhash("${General::swroot}/time/settings", \%timesettings);
112 &General::readhash("/srv/web/ipfire/html/themes/".$mainsettings{'THEME'}."/include/colors.txt", \%color);
113
114 #Get GUI values
115 &Header::getcgihash(\%dhcpsettings);
116
117 open(FILE, "$filename1") or die 'Unable to open dhcp advanced options file.';
118 our @current1 = <FILE>;
119 close(FILE);
120 # Extract OptionDefinition
121 foreach my $line (@current1) {
122 #chomp($line); # remove newline #don't know why, but this remove newline in @current1 .... !
123 my @temp = split(/\t/,$line);
124 AddNewOptionDefinition ($temp[1] . ' ' . $temp[2]);
125 }
126
127 open(FILE, "$filename2") or die 'Unable to open fixed leases file.';
128 our @current2 = <FILE>;
129 close(FILE);
130
131 # Check Settings1 first because they are needed by &buildconf
132 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'save'}) {
133 foreach my $itf (@ITFs) {
134 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on' ) {
135 # "Start" is defined, need "End" and vice versa
136 if ($dhcpsettings{"START_ADDR_${itf}"}) {
137 if (!(&General::validip($dhcpsettings{"START_ADDR_${itf}"}))) {
138 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid start address'};
139 goto ERROR;
140 }
141 if (!$dhcpsettings{"END_ADDR_${itf}"}) {
142 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid end address'};
143 goto ERROR;
144 }
145 if (! &General::IpInSubnet ( $dhcpsettings{"START_ADDR_${itf}"},
146 $netsettings{"${itf}_NETADDRESS"},
147 $netsettings{"${itf}_NETMASK"})) {
148 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid start address'};
149 goto ERROR;
150 }
151 }
152
153 if ($dhcpsettings{"END_ADDR_${itf}"}) {
154 if (!(&General::validip($dhcpsettings{"END_ADDR_${itf}"}))) {
155 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid end address'};
156 goto ERROR;
157 }
158 if (!$dhcpsettings{"START_ADDR_${itf}"}) {
159 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid start address'};
160 goto ERROR;
161 }
162 if (! &General::IpInSubnet ( $dhcpsettings{"END_ADDR_${itf}"},
163 $netsettings{"${itf}_NETADDRESS"},
164 $netsettings{"${itf}_NETMASK"})) {
165 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid end address'};
166 goto ERROR;
167 }
168 #swap if necessary! (support 255.255.0.0 range, I doubt we need more) GE
169 my @startoct = split (/\./, $dhcpsettings{"START_ADDR_${itf}"});
170 my @endoct = split (/\./, $dhcpsettings{"END_ADDR_${itf}"});
171 if ( $endoct[2]*256+$endoct[3] < $startoct[2]*256+$startoct[3] ) {
172 ($dhcpsettings{"START_ADDR_${itf}"},$dhcpsettings{"END_ADDR_${itf}"}) =
173 ($dhcpsettings{"END_ADDR_${itf}"},$dhcpsettings{"START_ADDR_${itf}"});
174 }
175 }
176
177 if (!($dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} =~ /^\d+$/)) {
178 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid default lease time'} . $dhcpsettings{'DEFAULT_LEASE_TIME_${itf}'};
179 goto ERROR;
180 }
181
182 if (!($dhcpsettings{"MAX_LEASE_TIME_${itf}"} =~ /^\d+$/)) {
183 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid max lease time'} . $dhcpsettings{'MAX_LEASE_TIME_${itf}'};
184 goto ERROR;
185 }
186
187 if ($dhcpsettings{"DNS1_${itf}"}) {
188 if (!(&General::validip($dhcpsettings{"DNS1_${itf}"}))) {
189 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid primary dns'};
190 goto ERROR;
191 }
192 }
193 if ($dhcpsettings{"DNS2_${itf}"}) {
194 if (!(&General::validip($dhcpsettings{"DNS2_${itf}"}))) {
195 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid secondary dns'};
196 goto ERROR;
197 }
198 if (! $dhcpsettings{"DNS1_${itf}"}) {
199 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'cannot specify secondary dns without specifying primary'};
200 goto ERROR;
201 }
202 }
203
204 if ($dhcpsettings{"WINS1_${itf}"}) {
205 if (!(&General::validip($dhcpsettings{"WINS1_${itf}"}))) {
206 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid wins address'};
207 goto ERROR;
208 }
209 }
210 if ($dhcpsettings{"WINS2_${itf}"}) {
211 if (!(&General::validip($dhcpsettings{"WINS2_${itf}"}))) {
212 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid wins address'};
213 goto ERROR;
214 }
215 if (! $dhcpsettings{"WINS1_${itf}"} ) {
216 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'cannot specify secondary wins without specifying primary'};
217 goto ERROR;
218 }
219 }
220 if ($dhcpsettings{"NEXT_${itf}"}) {
221 if (!(&General::validip($dhcpsettings{"NEXT_${itf}"}))) {
222 $errormessage = "next-server on ${itf}: " . $Lang::tr{'invalid ip'};
223 goto ERROR;
224 }
225 }
226 if ($dhcpsettings{"NTP1_${itf}"}) {
227 if (!(&General::validip($dhcpsettings{"NTP1_${itf}"}))) {
228 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid primary ntp'};
229 goto ERROR;
230 }
231 if ($dhcpsettings{"NTP1_${itf}"} eq $netsettings{"${itf}_ADDRESS"} && ($timesettings{'ENABLECLNTP'} ne 'on')) {
232 $warnNTPmessage = "DHCP on ${itf}: " . $Lang::tr{'local ntp server specified but not enabled'};
233 #goto ERROR;
234 }
235 }
236 if ($dhcpsettings{"NTP2_${itf}"}) {
237 if (!(&General::validip($dhcpsettings{"NTP2_${itf}"}))) {
238 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'invalid secondary ntp'};
239 goto ERROR;
240 }
241 if ($dhcpsettings{"NTP2_${itf}"} eq $netsettings{"${itf}_ADDRESS"} && ($timesettings{'ENABLECLNTP'} ne 'on')) {
242 $warnNTPmessage = "DHCP on ${itf}: " . $Lang::tr{'local ntp server specified but not enabled'};
243 #goto ERROR;
244 }
245 if (! $dhcpsettings{"NTP1_${itf}"}) {
246 $errormessage = "DHCP on ${itf}: " . $Lang::tr{'cannot specify secondary ntp without specifying primary'};
247 goto ERROR;
248 }
249 }
250 } # enabled
251 }#loop interface verify
252
253 map (delete ($dhcpsettings{$_}) ,@nosaved,'ACTION','KEY1','KEY2'); # Must not be saved
254 &General::writehash($setting, \%dhcpsettings); # Save good settings
255 $dhcpsettings{'ACTION'} = $Lang::tr{'save'}; # create an 'ACTION'
256 map ($dhcpsettings{$_} = '',@nosaved,'KEY1','KEY2'); # and reinit vars to empty
257 &buildconf;
258 ERROR: # Leave the faulty field untouched
259 } else {
260 &General::readhash($setting, \%dhcpsettings); # Get saved settings and reset to good if needed
261 }
262
263 ## Sorting of fixed leases
264 if ($ENV{'QUERY_STRING'} =~ /^FETHER|^FIPADDR/ ) {
265 my $newsort=$ENV{'QUERY_STRING'};
266 my $act=$dhcpsettings{'SORT_FLEASELIST'};
267 #Reverse actual sort ?
268 if ($act =~ $newsort) {
269 my $Rev='';
270 if ($act !~ 'Rev') {
271 $Rev='Rev';
272 }
273 $newsort.=$Rev;
274 }
275 $dhcpsettings{'SORT_FLEASELIST'}=$newsort;
276 map (delete ($dhcpsettings{$_}) ,@nosaved,'ACTION','KEY1','KEY2'); # Must never be saved
277 &General::writehash($setting, \%dhcpsettings);
278 &sortcurrent2;
279 $dhcpsettings{'ACTION'} = 'SORT'; # create an 'ACTION'
280 map ($dhcpsettings{$_} = '',@nosaved,'KEY1','KEY2');# and reinit vars to empty
281 }
282
283 #Sorting of allocated leases
284 &Header::CheckSortOrder;
285
286
287 ## Now manipulate the two multi-line list with Settings2.
288 # '1' suffix is for ADVANCED OPTIONS
289 # '2' suffix is for FIXED LEASES
290
291 # Toggle enable/disable field on specified options.
292
293 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'toggle enable disable'}.'1') {
294 #move out new line
295 chomp(@current1[$dhcpsettings{'KEY1'}]);
296 my @temp = split(/\t/,@current1[$dhcpsettings{'KEY1'}]); #use TAB separator !
297 $temp[0] = $temp[0] eq 'on' ? '' : 'on'; # Toggle the field
298 @current1[$dhcpsettings{'KEY1'}] = join ("\t",@temp)."\n";
299 $dhcpsettings{'KEY1'} = ''; # End edit mode
300 &General::log($Lang::tr{'dhcp advopt modified'});
301 open(FILE, ">$filename1") or die 'Unable to open dhcp advanced options file.';
302 print FILE @current1;
303 close(FILE);
304
305 #Write changes to dhcpd.conf.
306 &buildconf;
307 }
308
309
310
311 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'add'}.'1' &&
312 $dhcpsettings{'SUBMIT'} ne $Lang::tr{'dhcp advopt help'}) {
313 $dhcpsettings{'ADVOPT_NAME'} =~ s/[^ \w-]//g; # prevent execution of code by removing everything except letters/space
314 $dhcpsettings{'ADVOPT_DATA'} =~ s/`//g; # back tik ` ? not allowed !
315
316 if ($dhcpsettings{'ADVOPT_DATA'} eq '') {
317 $errormessage=$Lang::tr{'dhcp advopt blank value'};
318 }
319
320 # Test for a new option definition string (join field name & data)
321 if (ExistNewOptionDefinition ($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
322 #only edit permitted if option definition exists
323 $errormessage = $Lang::tr{'dhcp advopt definition exists'} if ($dhcpsettings{'KEY1'} eq '');
324 $dhcpsettings{'ADVOPT_ENABLED'} = 'on'; # force active
325 map ($dhcpsettings{"ADVOPT_SCOPE_$_"} = 'off', @ITFs); # force global
326 } elsif (AddNewOptionDefinition ($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
327 #was a new option definition
328 $dhcpsettings{'ADVOPT_ENABLED'} = 'on'; # force active
329 map ($dhcpsettings{"ADVOPT_SCOPE_$_"} = 'off', @ITFs); # force global
330 } elsif (ValidNewOption ($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
331 #was a new option
332 } elsif (! `grep "\$option $dhcpsettings{'ADVOPT_NAME'} " $filename3`) {
333 $errormessage=$Lang::tr{'dhcp advopt unknown'}.': '.$dhcpsettings{'ADVOPT_NAME'};
334 }
335
336 unless ($errormessage) {
337
338 my $scope = '';
339 foreach my $itf (@ITFs) { # buils "RED,GREEN,ORANGE,... based on selection
340 $scope .= $dhcpsettings{"ADVOPT_SCOPE_${itf}"} eq 'on' ? "\t$itf" : "\toff" ;
341 }
342 if ($dhcpsettings{'KEY1'} eq '') { #add or edit ? TAB separator !
343 unshift (@current1, "$dhcpsettings{'ADVOPT_ENABLED'}\t$dhcpsettings{'ADVOPT_NAME'}\t$dhcpsettings{'ADVOPT_DATA'}$scope\n");
344 &General::log($Lang::tr{'dhcp advopt added'});
345 } else {
346 @current1[$dhcpsettings{'KEY1'}] = "$dhcpsettings{'ADVOPT_ENABLED'}\t$dhcpsettings{'ADVOPT_NAME'}\t$dhcpsettings{'ADVOPT_DATA'}$scope\n";
347 $dhcpsettings{'KEY1'} = ''; # End edit mode
348 &General::log($Lang::tr{'dhcp advopt modified'});
349 }
350
351 #Write changes to dhcpd.conf.
352 &sortcurrent1; # sort newly added/modified entry
353 &buildconf; # before calling buildconf which use fixed lease file !
354 }
355 }
356
357 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'edit'}.'1') {
358 #move out new line
359 my $line = @current1[$dhcpsettings{'KEY1'}];
360 chomp($line);
361 my @temp = split(/\t/, $line);
362 $dhcpsettings{'ADVOPT_ENABLED'}=$temp[0];
363 $dhcpsettings{'ADVOPT_NAME'}=$temp[1];
364 $dhcpsettings{'ADVOPT_DATA'}=$temp[2];
365
366 # read next fields which are the name (color) of an interface if this interface is scoped
367 for (my $key=0; $key<@ITFs; $key++) {
368 my $itf = $temp[3+$key];
369 if ($itf ne 'off') # Only is an interface name is read
370 {
371 $dhcpsettings{"ADVOPT_SCOPE_${itf}"} = 'on';
372 }
373 }
374 }
375
376 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'remove'}.'1') {
377 splice (@current1,$dhcpsettings{'KEY1'},1);
378 open(FILE, ">$filename1") or die 'Unable to open dhcp advanced options file.';
379 print FILE @current1;
380 close(FILE);
381 $dhcpsettings{'KEY1'} = ''; # End remove mode
382 &General::log($Lang::tr{'dhcp advopt removed'});
383 #Write changes to dhcpd.conf.
384 &buildconf;
385 }
386 #end KEY1
387
388
389 # Toggle enable/disable field on specified lease.
390 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'toggle enable disable'}.'2') {
391 #move out new line
392 chomp(@current2[$dhcpsettings{'KEY2'}]);
393 my @temp = split(/\,/,@current2[$dhcpsettings{'KEY2'}]);
394 $temp[2] = $temp[2] eq 'on' ? '' : 'on'; # Toggle the field
395 @current2[$dhcpsettings{'KEY2'}] = join (',',@temp)."\n";
396 $dhcpsettings{'KEY2'} = ''; # End edit mode
397 &General::log($Lang::tr{'fixed ip lease modified'});
398 open(FILE, ">$filename2") or die 'Unable to open fixed leases file.';
399 print FILE @current2;
400 close(FILE);
401
402 #Write changes to dhcpd.conf.
403 &buildconf;
404 }
405
406 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'add'}.'2') {
407 $dhcpsettings{'FIX_MAC'} =~ tr/-/:/;
408 unless(&General::validip($dhcpsettings{'FIX_ADDR'})) { $errormessage = $Lang::tr{'invalid fixed ip address'}; }
409 unless(&General::validmac($dhcpsettings{'FIX_MAC'})) { $errormessage = $Lang::tr{'invalid fixed mac address'}; }
410 if ($dhcpsettings{'FIX_NEXTADDR'}) {
411 unless(&General::validip($dhcpsettings{'FIX_NEXTADDR'})) { $errormessage = $Lang::tr{'invalid fixed ip address'}; }
412 }
413
414 my $key = 0;
415 CHECK:foreach my $line (@current2) {
416 my @temp = split(/\,/,$line);
417 if($dhcpsettings{'KEY2'} ne $key) {
418 # same MAC is OK on different subnets. This test is not complete because
419 # if ip are not inside a known subnet, I don't warn.
420 # Also it may be needed to put duplicate fixed lease in their right subnet definition..
421 foreach my $itf (@ITFs) {
422 my $scoped = &General::IpInSubnet($dhcpsettings{'FIX_ADDR'},
423 $netsettings{"${itf}_NETADDRESS"},
424 $netsettings{"${itf}_NETMASK"}) &&
425 $dhcpsettings{"ENABLE_${itf}"} eq 'on';
426 if ( $scoped &&
427 (lc($dhcpsettings{'FIX_MAC'}) eq lc($temp[0])) &&
428 &General::IpInSubnet($temp[1],
429 $netsettings{"${itf}_NETADDRESS"},
430 $netsettings{"${itf}_NETMASK"})) {
431 $errormessage = "$Lang::tr{'mac address in use'} $dhcpsettings{'FIX_MAC'}";
432 last CHECK;
433 }
434 }
435 }
436 $key++;
437 }
438
439 unless ($errormessage) {
440 $dhcpsettings{'FIX_REMARK'} = &Header::cleanhtml($dhcpsettings{'FIX_REMARK'});
441 $dhcpsettings{'FIX_NEXTADDR'} = &Header::cleanhtml($dhcpsettings{'FIX_NEXTADDR'});
442 $dhcpsettings{'FIX_FILENAME'} = &Header::cleanhtml($dhcpsettings{'FIX_FILENAME'});
443 $dhcpsettings{'FIX_ROOTPATH'} = &Header::cleanhtml($dhcpsettings{'FIX_ROOTPATH'});
444 if ($dhcpsettings{'KEY2'} eq '') { #add or edit ?
445 unshift (@current2, "$dhcpsettings{'FIX_MAC'},$dhcpsettings{'FIX_ADDR'},$dhcpsettings{'FIX_ENABLED'},$dhcpsettings{'FIX_NEXTADDR'},$dhcpsettings{'FIX_FILENAME'},$dhcpsettings{'FIX_ROOTPATH'},$dhcpsettings{'FIX_REMARK'}\n");
446 &General::log($Lang::tr{'fixed ip lease added'});
447 } else {
448 @current2[$dhcpsettings{'KEY2'}] = "$dhcpsettings{'FIX_MAC'},$dhcpsettings{'FIX_ADDR'},$dhcpsettings{'FIX_ENABLED'},$dhcpsettings{'FIX_NEXTADDR'},$dhcpsettings{'FIX_FILENAME'},$dhcpsettings{'FIX_ROOTPATH'},$dhcpsettings{'FIX_REMARK'}\n";
449 $dhcpsettings{'KEY2'} = ''; # End edit mode
450 &General::log($Lang::tr{'fixed ip lease modified'});
451 }
452
453 #Write changes to dhcpd.conf.
454 &sortcurrent2; # sort newly added/modified entry
455 &buildconf; # before calling buildconf which use fixed lease file !
456 }
457 }
458
459 if ($dhcpsettings{'ACTION_ALL'} eq '+') {
460 my $news = 0;
461 foreach (keys %dhcpsettings) {
462 if (/^(\d+\.\d+\.\d+\.\d+)-([0-9a-fA-F:]+)$/) { # checked names are index of the line
463 my $ip=$1;
464 my $mac=$2;
465 if (!grep (/$2/,@current2)) {
466 unshift (@current2, "$mac,$ip,on,,,,imported\n");
467 $news++;
468 }
469 }
470 }
471 if ($news) {
472 #Write changes to dhcpd.conf.
473 $warnNTPmessage = $Lang::tr{'fixed ip lease added'}."($news)";
474 &General::log($warnNTPmessage);
475 &sortcurrent2; # sort newly added/modified entry
476 &buildconf; # before calling buildconf which use fixed lease file !
477 }
478 }
479
480 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'edit'}.'2') {
481 #move out new line
482 my $line = @current2[$dhcpsettings{'KEY2'}];
483 chomp($line);
484 my @temp = split(/\,/, $line);
485 $dhcpsettings{'FIX_MAC'}=$temp[0];
486 $dhcpsettings{'FIX_ADDR'}=$temp[1];
487 $dhcpsettings{'FIX_ENABLED'}=$temp[2];
488 $dhcpsettings{'FIX_NEXTADDR'}=$temp[3];
489 $dhcpsettings{'FIX_FILENAME'}=$temp[4];
490 $dhcpsettings{'FIX_ROOTPATH'}=$temp[5];
491 $dhcpsettings{'FIX_REMARK'}=$temp[6];
492 }
493
494 if ($dhcpsettings{'ACTION'} eq $Lang::tr{'remove'}.'2') {
495 splice (@current2,$dhcpsettings{'KEY2'},1);
496 open(FILE, ">$filename2") or die 'Unable to open fixed lease file.';
497 print FILE @current2;
498 close(FILE);
499 $dhcpsettings{'KEY2'} = ''; # End remove mode
500 &General::log($Lang::tr{'fixed ip lease removed'});
501 #Write changes to dhcpd.conf.
502 &buildconf;
503 }
504 #end KEY2 defined
505
506
507
508
509 if ($dhcpsettings{'ACTION'} eq '' ) { # First launch from GUI
510
511 # Set default DHCP values only if blank and disabled
512 foreach my $itf (@ITFs) {
513 if ($dhcpsettings{"ENABLE_${itf}"} ne 'on' ) {
514 $dhcpsettings{"DNS1_${itf}"} = $netsettings{"${itf}_ADDRESS"};
515 $dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} = '60';
516 $dhcpsettings{"MAX_LEASE_TIME_${itf}"} = '120';
517 $dhcpsettings{"DOMAIN_NAME_${itf}"} = $mainsettings{'DOMAINNAME'};
518 }
519 }
520 $dhcpsettings{'FIX_ENABLED'} = 'on';
521 }
522
523 &Header::openpage($Lang::tr{'dhcp configuration'}, 1, '');
524 &Header::openbigbox('100%', 'left', '', $errormessage);
525
526 if ($errormessage) {
527 &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
528 print "<font class='base'>$errormessage&nbsp;</font>\n";
529 &Header::closebox();
530 }
531 if ($warnNTPmessage) {
532 $warnNTPmessage = "<font color=${Header::colourred}><b>$Lang::tr{'capswarning'}</b></font>: $warnNTPmessage";
533 }
534
535 &Header::openbox('100%', 'left', 'DHCP');
536 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>";
537
538 foreach my $itf (@ITFs) {
539 my %checked=();
540 $checked{'ENABLE'}{'on'} = ( $dhcpsettings{"ENABLE_${itf}"} ne 'on') ? '' : "checked='checked'";
541 $checked{'ENABLEBOOTP'}{'on'} = ( $dhcpsettings{"ENABLEBOOTP_${itf}"} ne 'on') ? '' : "checked='checked'";
542
543 if ($netsettings{"${itf}_DEV"} ne '' ) { # Show only defined interface
544 my $lc_itf=lc($itf);
545 print <<END
546 <table width='100%'>
547 <tr>
548 <td width='25%' class='boldbase'><b><font color='${lc_itf}'>$Lang::tr{"$lc_itf interface"}</font></b></td>
549 <td class='base'>$Lang::tr{'enabled'}
550 <input type='checkbox' name='ENABLE_${itf}' $checked{'ENABLE'}{'on'} /></td>
551 <td width='25%' class='base'>$Lang::tr{'ip address'}<br />$Lang::tr{'netmask'}:</td><td><b>$netsettings{"${itf}_ADDRESS"}<br />$netsettings{"${itf}_NETMASK"}</b></td>
552 </tr><tr>
553 <td width='25%' class='base'>$Lang::tr{'start address'}</td>
554 <td width='25%'><input type='text' name='START_ADDR_${itf}' value='$dhcpsettings{"START_ADDR_${itf}"}' /></td>
555 <td width='25%' class='base'>$Lang::tr{'end address'}</td>
556 <td width='25%'><input type='text' name='END_ADDR_${itf}' value='$dhcpsettings{"END_ADDR_${itf}"}' /></td>
557 </tr><tr>
558 <td class='base'>$Lang::tr{'default lease time'}</td>
559 <td><input type='text' name='DEFAULT_LEASE_TIME_${itf}' value='$dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"}' /></td>
560 <td class='base'>$Lang::tr{'max lease time'}</td>
561 <td><input type='text' name='MAX_LEASE_TIME_${itf}' value='$dhcpsettings{"MAX_LEASE_TIME_${itf}"}' /></td>
562 </tr><tr>
563 <td class='base'>$Lang::tr{'domain name suffix'}&nbsp;<img src='/blob.gif' alt='*' /></td>
564 <td><input type='text' name='DOMAIN_NAME_${itf}' value='$dhcpsettings{"DOMAIN_NAME_${itf}"}' /></td>
565 <td>$Lang::tr{'dhcp allow bootp'}:</td>
566 <td><input type='checkbox' name='ENABLEBOOTP_${itf}' $checked{'ENABLEBOOTP'}{'on'} /></td>
567 </tr><tr>
568 <td class='base'>$Lang::tr{'primary dns'}</td>
569 <td><input type='text' name='DNS1_${itf}' value='$dhcpsettings{"DNS1_${itf}"}' /></td>
570 <td class='base'>$Lang::tr{'secondary dns'}&nbsp;<img src='/blob.gif' alt='*' /></td>
571 <td><input type='text' name='DNS2_${itf}' value='$dhcpsettings{"DNS2_${itf}"}' /></td>
572 </tr><tr>
573 <td class='base'>$Lang::tr{'primary ntp server'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
574 <td><input type='text' name='NTP1_${itf}' value='$dhcpsettings{"NTP1_${itf}"}' /></td>
575 <td class='base'>$Lang::tr{'secondary ntp server'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
576 <td><input type='text' name='NTP2_${itf}' value='$dhcpsettings{"NTP2_${itf}"}' /></td>
577 </tr><tr>
578 <td class='base'>$Lang::tr{'primary wins server address'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
579 <td><input type='text' name='WINS1_${itf}' value='$dhcpsettings{"WINS1_${itf}"}' /></td>
580 <td class='base'>$Lang::tr{'secondary wins server address'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
581 <td><input type='text' name='WINS2_${itf}' value='$dhcpsettings{"WINS2_${itf}"}' /></td>
582 </tr><tr>
583 <td class='base'>next-server:&nbsp;<img src='/blob.gif' alt='*' /></td>
584 <td><input type='text' name='NEXT_${itf}' value='$dhcpsettings{"NEXT_${itf}"}' /></td>
585 <td class='base'>filename:&nbsp;<img src='/blob.gif' alt='*' /></td>
586 <td><input type='text' name='FILE_${itf}' value='$dhcpsettings{"FILE_${itf}"}' /></td>
587 </tr>
588 </table>
589 <hr />
590 END
591 ;
592 }# Show only defined interface
593 }#foreach itf
594 print <<END
595 <table width='100%'>
596 <tr>
597 <td class='base' width='25%'><img src='/blob.gif' align='top' alt='*' />&nbsp;$Lang::tr{'this field may be blank'}</td>
598 <td class='base' width='30%'>$warnNTPmessage</td>
599 <td width='40%' align='right'><input type='submit' name='ACTION' value='$Lang::tr{'save'}' /></td>
600 </tr>
601 </table>
602 </form>
603 END
604 ;
605
606 &Header::closebox();
607
608 &Header::openbox('100%', 'left', $Lang::tr{'dhcp advopt list'});
609 # DHCP Advanced options settings
610 my %checked=();
611 $checked{'ADVOPT_ENABLED'}{'on'} = ($dhcpsettings{'ADVOPT_ENABLED'} ne 'on') ? '' : "checked='checked'";
612
613 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'><table width='100%'>";
614 my $buttontext = $Lang::tr{'add'};
615 if ($dhcpsettings{'KEY1'} ne '') {
616 $buttontext = $Lang::tr{'update'};
617 print "<tr><td class='boldbase'><b>$Lang::tr{'dhcp advopt edit'}</b></td></tr>";
618 } else {
619 print "<tr><td class='boldbase'><b>$Lang::tr{'dhcp advopt add'}</b></td></tr>"
620 }
621
622 #search if the 'option' is in the list and print the syntax model
623 my $opt = `grep "\$option $dhcpsettings{'ADVOPT_NAME'} " $filename3`;
624 if ($opt ne '') {
625 $opt =~ s/option $dhcpsettings{'ADVOPT_NAME'}/Syntax:/; # "option xyz abc" => "syntax: abc"
626 $opt =~ s/;//;
627 $opt = "<tr><td></td><td></td><td colspan='2'>$opt</td></tr>";
628 }
629 print <<END
630 <tr>
631 <td class='base'>$Lang::tr{'dhcp advopt name'}:</td>
632 <td><input type='text' name='ADVOPT_NAME' value='$dhcpsettings{'ADVOPT_NAME'}' size='18' /></td>
633 <td class='base'>$Lang::tr{'dhcp advopt value'}:</td>
634 <td><input type='text' name='ADVOPT_DATA' value='$dhcpsettings{'ADVOPT_DATA'}' size='40' /></td>
635 </tr>$opt<tr>
636 <td class='base'>$Lang::tr{'enabled'}</td><td><input type='checkbox' name='ADVOPT_ENABLED' $checked{'ADVOPT_ENABLED'}{'on'} /></td>
637 <td class='base'>$Lang::tr{'dhcp advopt scope'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
638 <td>
639 END
640 ;
641
642 # Put a checkbox for each interface. Checkbox visible disabled if interface is disabled
643 foreach my $itf (@ITFs) {
644 my $lc_itf=lc($itf);
645 $checked{'ADVOPT_SCOPE_${itf}'}{'on'} = $dhcpsettings{"ADVOPT_SCOPE_${itf}"} ne 'on' ? '' : "checked='checked'";
646 print "$Lang::tr{\"${lc_itf}\"} <input type='checkbox' name='ADVOPT_SCOPE_${itf}' $checked{'ADVOPT_SCOPE_${itf}'}{'on'} ";
647 print $dhcpsettings{"ENABLE_${itf}"} eq 'on' ? "/>" : "disabled='disabled' />";
648 print "&nbsp; &nbsp;";
649 }
650
651 print <<END
652 </td>
653 </tr>
654 </table>
655 <hr />
656 <table width='100%'>
657 <tr>
658 <td class='base' width='50%'><img src='/blob.gif' align='top' alt='*' />&nbsp;$Lang::tr{'dhcp advopt scope help'}</td>
659 <td width='50%' align='right'>
660 <input type='hidden' name='ACTION' value='$Lang::tr{'add'}1' />
661 <input type='submit' name='SUBMIT' value='$buttontext' />
662 <input type='submit' name='SUBMIT' value='$Lang::tr{'dhcp advopt help'}' />
663 <input type='hidden' name='KEY1' value='$dhcpsettings{'KEY1'}' />
664 </td>
665 </tr>
666 </table>
667 </form>
668 END
669 ;
670 #Edited line number (KEY1) passed until cleared by 'save' or 'remove' or 'new sort order'
671
672 # print help taken from the file describing options
673 if ($dhcpsettings{'SUBMIT'} eq $Lang::tr{'dhcp advopt help'}) {
674 print "<hr />";
675 print "<table width='100%'>";
676 print "<tr><td width='30%'><b>$Lang::tr{'dhcp advopt name'}</b></td><td width='70%'><b>$Lang::tr{'dhcp advopt value'}</b></td>";
677 open(FILE, "$filename3");
678 my @current3 = <FILE>;
679 close(FILE);
680 foreach my $line (@current3) {
681 $line =~ /option ([a-z0-9-]+) (.*);/;
682 print "<tr><td>$1</td><td>$2</td></tr>\n";
683 }
684 print "<tr><td colspan='2'><hr /></td></tr>\n";
685 print '<tr><td>string type</td><td>"quoted string" or 00:01:FF...</td></tr>';
686 print '<tr><td>ip-address type </td><td>10.0.0.1 | www.dot.com</td></tr>';
687 print '<tr><td>int,uint types</td><td>numbers</td></tr>';
688 print '<tr><td>flag type</td><td>on | off</td></tr>';
689 print '</table>';
690 print "<hr />";
691 print "<table width='100%'>";
692 print "<tr><td width='30%'><b>$Lang::tr{'dhcp advopt custom definition'}</b></td><td width='70%'><b>$Lang::tr{'dhcp advopt value'}</b></td>";
693 print "<tr><td>any-name </td><td> code NNN=$OptionTypes</td></tr>";
694 print '<tr><td>a-string</td><td>code 100=string</td></tr>';
695 print '<tr><td>a-number</td><td>code 101=signed integer 8</td></tr>';
696 print '<tr><td>wpad</td><td>code 252=text</td></tr>';
697 print '<tr><td>wpad</td><td>"http://www.server.fr/path-to/proxy.pac"</td></tr>';
698 print '</table>';
699
700 }
701
702 print <<END
703 <hr />
704 <table width='100%'>
705 <tr>
706 <td width='30%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt name'}</b></td>
707 <td width='50%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt value'}</b></td>
708 <td width='20%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt scope'}</b></td>
709 <td colspan='3' class='boldbase' align='center'><b>$Lang::tr{'action'}</b></td>
710 </tr>
711 END
712 ;
713 my $key = 0;
714 foreach my $line (@current1) {
715 my $gif = '';
716 my $gdesc = '';
717 chomp($line); # remove newline
718 my @temp = split(/\t/,$line);
719
720 if ($temp[0] eq "on") {
721 $gif = 'on.gif';
722 $gdesc = $Lang::tr{'click to disable'};
723 } else {
724 $gif = 'off.gif';
725 $gdesc = $Lang::tr{'click to enable'};
726 }
727
728 if ($dhcpsettings{'KEY1'} eq $key) {
729 print "<tr bgcolor='${Header::colouryellow}'>";
730 } elsif ($key % 2) {
731 print "<tr bgcolor='$color{'color22'}'>";
732 } else {
733 print "<tr bgcolor='$color{'color20'}'>";
734 }
735
736 print <<END
737 <td align='center'>$temp[1]</td>
738 <td align='center'>$temp[2]</td>
739 <td align='center'>
740 END
741 ;
742 # Prepare a global flag to make easy reading
743 my $global = '';
744 my $disabledTogle = '';
745 my $disabledEditRemove = '';
746 if ( ExistNewOptionDefinition ($temp[1] . ' ' . $temp[2]) ) {
747 $global = $Lang::tr{'dhcp advopt definition'};
748 $disabledTogle = "disabled='disabled'";
749 # Search if it is a used NewOptionDefinition to also disable edit & delete
750 $disabledEditRemove = "disabled='disabled'" if (IsUsedNewOptionDefinition ($temp[1], $temp[2]));
751 } else {
752 $global = $Lang::tr{'dhcp advopt scope global'};
753 }
754
755
756 # Print each checked interface
757 for (my $key=0; $key<@ITFs; $key++) {
758 my $itf = $temp[3+$key];
759 if ($itf ne 'off') { # Only if an interface name is read
760 print "$itf";
761 $global=''; # fall to local scope !
762 }
763 }
764 print <<END
765 $global</td>
766 <td align='center'>
767 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
768 <input type='hidden' name='ACTION' value='$Lang::tr{'toggle enable disable'}1' />
769 <input $disabledTogle type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc' />
770 <input type='hidden' name='KEY1' value='$key' />
771 </form>
772 </td>
773
774 <td align='center'>
775 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
776 <input type='hidden' name='ACTION' value='$Lang::tr{'edit'}1' />
777 <input $disabledEditRemove type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}' />
778 <input type='hidden' name='KEY1' value='$key' />
779 </form>
780 </td>
781
782 <td align='center'>
783 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
784 <input type='hidden' name='ACTION' value='$Lang::tr{'remove'}1' />
785 <input $disabledEditRemove type='image' name='$Lang::tr{'remove'}' src='/images/delete.gif' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' />
786 <input type='hidden' name='KEY1' value='$key' />
787 </form>
788 </td>
789 </tr>
790 END
791 ;
792 $key++;
793 }
794
795 print "</table>";
796
797 # If there are dhcp options, print Key to action icons
798 if ($key) {
799 print <<END
800 <table>
801 <tr>
802 <td class='boldbase'>&nbsp;<b>$Lang::tr{'legend'}:&nbsp;</b></td>
803 <td><img src='/images/on.gif' alt='$Lang::tr{'click to disable'}' /></td>
804 <td class='base'>$Lang::tr{'click to disable'}</td>
805 <td>&nbsp;&nbsp;</td>
806 <td><img src='/images/off.gif' alt='$Lang::tr{'click to enable'}' /></td>
807 <td class='base'>$Lang::tr{'click to enable'}</td>
808 <td>&nbsp;&nbsp;</td>
809 <td><img src='/images/edit.gif' alt='$Lang::tr{'edit'}' /></td>
810 <td class='base'>$Lang::tr{'edit'}</td>
811 <td>&nbsp;&nbsp;</td>
812 <td><img src='/images/delete.gif' alt='$Lang::tr{'remove'}' /></td>
813 <td class='base'>$Lang::tr{'remove'}</td>
814 </tr>
815 </table>
816 END
817 ;
818 }
819 &Header::closebox();
820
821 &Header::openbox('100%', 'left', $Lang::tr{'current fixed leases'});
822 # Fixed leases screens
823 $checked{'FIX_ENABLED'}{'on'} = ($dhcpsettings{'FIX_ENABLED'} ne 'on') ? '' : "checked='checked'";
824
825 $buttontext = $Lang::tr{'add'};
826 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'><table width='100%'>";
827
828 if ($dhcpsettings{'KEY2'} ne '') {
829 $buttontext = $Lang::tr{'update'};
830 print "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'edit an existing lease'}</b></td></tr>";
831 } else {
832 print "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'add new lease'}</b></td></tr>"
833 }
834 print <<END
835 <tr>
836 <td class='base'>$Lang::tr{'mac address'}:</td>
837 <td><input type='text' name='FIX_MAC' value='$dhcpsettings{'FIX_MAC'}' size='18' /></td>
838 <td class='base'>$Lang::tr{'ip address'}:</td>
839 <td><input type='text' name='FIX_ADDR' value='$dhcpsettings{'FIX_ADDR'}' size='18' /></td>
840 <td class='base'>$Lang::tr{'remark'}:&nbsp;<img src='/blob.gif' alt='*' /></td>
841 <td><input type='text' name='FIX_REMARK' value='$dhcpsettings{'FIX_REMARK'}' size='18' /></td>
842 </tr><tr>
843 <td class='base'>$Lang::tr{'enabled'}</td><td><input type='checkbox' name='FIX_ENABLED' $checked{'FIX_ENABLED'}{'on'} /></td>
844 </tr><tr>
845 <td colspan = '3'><b>$Lang::tr{'dhcp bootp pxe data'}</b></td>
846 </tr><tr>
847 <td class='base'>next-server:&nbsp;<img src='/blob.gif' alt='*' /></td>
848 <td><input type='text' name='FIX_NEXTADDR' value='$dhcpsettings{'FIX_NEXTADDR'}' size='18' /></td>
849 <td class='base'>filename:&nbsp;<img src='/blob.gif' alt='*' /></td>
850 <td><input type='text' name='FIX_FILENAME' value='$dhcpsettings{'FIX_FILENAME'}' size='18' /></td>
851 <td class='base'>root path:&nbsp;<img src='/blob.gif' alt='*' /></td>
852 <td><input type='text' name='FIX_ROOTPATH' value='$dhcpsettings{'FIX_ROOTPATH'}' size='18' /></td>
853 </tr>
854 </table>
855 <hr />
856 <table width='100%'>
857 <tr>
858 <td class='base' width='50%'><img src='/blob.gif' align='top' alt='*' />&nbsp;$Lang::tr{'this field may be blank'}</td>
859 <td width='50%' align='right'>
860 <input type='hidden' name='ACTION' value='$Lang::tr{'add'}2' />
861 <input type='submit' name='SUBMIT' value='$buttontext' />
862 <input type='hidden' name='KEY2' value='$dhcpsettings{'KEY2'}' /></td>
863 </tr>
864 </table>
865 </form>
866 END
867 ;
868 #Edited line number (KEY2) passed until cleared by 'save' or 'remove' or 'new sort order'
869
870 print <<END
871 <hr />
872 <table width='100%' class='tbl'>
873 <tr>
874 <th width='20%' align='center'><a href='$ENV{'SCRIPT_NAME'}?FETHER'><b>$Lang::tr{'mac address'}</b></a></th>
875 <th width='20%' align='center'><a href='$ENV{'SCRIPT_NAME'}?FIPADDR'><b>$Lang::tr{'ip address'}</b></a></th>
876 <th width='15%' align='center'><b>$Lang::tr{'remark'}</b></th>
877 <th width='15%' class='boldbase' align='center'><b>next-server</b></th>
878 <th width='15%' class='boldbase' align='center'><b>filename</b></th>
879 <th width='15%' class='boldbase' align='center'><b>root path</b></th>
880 <th colspan='3' class='boldbase' align='center'><b>$Lang::tr{'action'}</b></th>
881 </tr>
882 END
883 ;
884 my $ipdup = 0;
885 my %ipinuse = ();
886 my %macdupl = (); # Duplicate MACs have to be on different subnets
887 my %ipoutside = ();
888
889 # mark duplicate ip or duplicate MAC
890 foreach my $line (@current2) {
891 my @temp = split(/\,/,$line);
892 $macdupl{$temp[0]} += 1;
893 if ($macdupl{$temp[0]} > 1) {
894 $ipdup = 1; # Flag up duplicates for use later
895 }
896 $ipinuse{$temp[1]} += 1;
897 if ($ipinuse{$temp[1]} > 1) {
898 $ipdup = 1; # Flag up duplicates for use later
899 }
900 # Mark IP addresses outwith known subnets
901 $ipoutside{$temp[1]} = 1;
902 foreach my $itf (@ITFs) {
903 if ( &General::IpInSubnet($temp[1],
904 $netsettings{"${itf}_NETADDRESS"},
905 $netsettings{"${itf}_NETMASK"})) {
906 $ipoutside{$temp[1]} = 0;
907 }
908 }
909 }
910
911 $key = 0;
912 my $col="";
913 foreach my $line (@current2) {
914 my $gif = '';
915 my $gdesc = '';
916 chomp($line); # remove newline
917 my @temp = split(/\,/,$line);
918
919 if ($temp[2] eq "on") {
920 $gif = 'on.gif';
921 $gdesc = $Lang::tr{'click to disable'};
922 } else {
923 $gif = 'off.gif';
924 $gdesc = $Lang::tr{'click to enable'};
925 }
926
927 if ($dhcpsettings{'KEY2'} eq $key) {
928 print "<tr>";
929 $col="bgcolor='${Header::colouryellow}'";
930 } elsif ($key % 2) {
931 print "<tr>";
932 $col="bgcolor='$color{'color20'}'";
933 } else {
934 print "<tr>";
935 $col="bgcolor='$color{'color22'}'";
936 }
937 my $TAG0 = '';
938 my $TAG1 = '';
939 my $TAG2 = '';
940 my $TAG3 = '';
941 my $TAG4 = '';
942 if ($ipinuse{$temp[1]} > 1) {
943 $TAG0 = '<b>';
944 $TAG1 = '</b>';
945 }
946 if ($macdupl{$temp[0]} > 1) {
947 $TAG2 = '<b>';
948 $TAG3 = '</b>';
949 }
950 if ($ipoutside{$temp[1]} > 0) {
951 $TAG4 = "bgcolor='orange'" if ($dhcpsettings{'KEY2'} ne $key);
952 }
953
954 print <<END
955 <td align='center' $col>$TAG2$temp[0]$TAG3</td>
956 <td align='center' $col $TAG4>$TAG0$temp[1]$TAG1</td>
957 <td align='center' $col>$temp[6]&nbsp;</td>
958 <td align='center' $col>$temp[3]&nbsp;</td>
959 <td align='center' $col>$temp[4]&nbsp;</td>
960 <td align='center' $col>$temp[5]&nbsp;</td>
961
962 <td align='center' $col>
963 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
964 <input type='hidden' name='ACTION' value='$Lang::tr{'toggle enable disable'}2' />
965 <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc' />
966 <input type='hidden' name='KEY2' value='$key' />
967 </form>
968 </td>
969
970 <td align='center' $col>
971 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
972 <input type='hidden' name='ACTION' value='$Lang::tr{'edit'}2' />
973 <input type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}' />
974 <input type='hidden' name='KEY2' value='$key' />
975 </form>
976 </td>
977
978 <td align='center' $col>
979 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
980 <input type='hidden' name='ACTION' value='$Lang::tr{'remove'}2' />
981 <input type='image' name='$Lang::tr{'remove'}' src='/images/delete.gif' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' />
982 <input type='hidden' name='KEY2' value='$key' />
983 </form>
984 </td>
985 </tr>
986 END
987 ;
988 $key++;
989 }
990 print "</table>";
991
992 # If the fixed lease file contains entries, print Key to action icons
993 if ($key) {
994 my $dup = $ipdup ? "<td class='base'>$Lang::tr{'duplicate ip bold'}</td>" :'';
995 print <<END
996 <table>
997 <tr>
998 <td class='boldbase'>&nbsp;<b>$Lang::tr{'legend'}:&nbsp;</b></td>
999 <td><img src='/images/on.gif' alt='$Lang::tr{'click to disable'}' /></td>
1000 <td class='base'>$Lang::tr{'click to disable'}</td>
1001 <td>&nbsp;&nbsp;</td>
1002 <td><img src='/images/off.gif' alt='$Lang::tr{'click to enable'}' /></td>
1003 <td class='base'>$Lang::tr{'click to enable'}</td>
1004 <td>&nbsp;&nbsp;</td>
1005 <td><img src='/images/edit.gif' alt='$Lang::tr{'edit'}' /></td>
1006 <td class='base'>$Lang::tr{'edit'}</td>
1007 <td>&nbsp;&nbsp;</td>
1008 <td><img src='/images/delete.gif' alt='$Lang::tr{'remove'}' /></td>
1009 <td class='base'>$Lang::tr{'remove'}</td>
1010 </tr>
1011 <tr>
1012 <td>&nbsp;</td>
1013 <td bgcolor='orange'>&nbsp;</td>
1014 <td class='base'>$Lang::tr{'ip address outside subnets'}</td>
1015 <td>&nbsp;</td>
1016 <td>&nbsp;</td>
1017 $dup
1018 </tr>
1019 </table>
1020 END
1021 ;
1022 }
1023
1024 &Header::closebox();
1025
1026 foreach my $itf (@ITFs) {
1027 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on') {
1028 # display leases with a list of actions to do with the global select checkbox.
1029 &Header::PrintActualLeases("+"); # "+" => create fixed leases from nodeaddress
1030 last; #Print one time only for all interfaces
1031 };
1032 }
1033
1034 &Header::closebigbox();
1035 &Header::closepage();
1036
1037 ## Ouf it's the end !
1038
1039 sub sortcurrent1 # by now, do not sort, just write
1040 {
1041 open(FILE, ">$filename1") or die 'Unable to open dhcp advanced options file.';
1042 print FILE @current1;
1043 close(FILE);
1044 }
1045
1046
1047 # Sort the "current2" array according to choices
1048 sub sortcurrent2
1049 {
1050 our %entries = ();
1051
1052 sub fixedleasesort {
1053 my $qs='';
1054 if (rindex ($dhcpsettings{'SORT_FLEASELIST'},'Rev') != -1) {
1055 $qs=substr ($dhcpsettings{'SORT_FLEASELIST'},0,length($dhcpsettings{'SORT_FLEASELIST'})-3);
1056 if ($qs eq 'FIPADDR') {
1057 my @a = split(/\./,$entries{$a}->{$qs});
1058 my @b = split(/\./,$entries{$b}->{$qs});
1059 ($b[0]<=>$a[0]) ||
1060 ($b[1]<=>$a[1]) ||
1061 ($b[2]<=>$a[2]) ||
1062 ($b[3]<=>$a[3]);
1063 } else {
1064 $entries{$b}->{$qs} cmp $entries{$a}->{$qs};
1065 }
1066 } else { #not reverse
1067 $qs=$dhcpsettings{'SORT_FLEASELIST'};
1068 if ($qs eq 'FIPADDR') {
1069 my @a = split(/\./,$entries{$a}->{$qs});
1070 my @b = split(/\./,$entries{$b}->{$qs});
1071 ($a[0]<=>$b[0]) ||
1072 ($a[1]<=>$b[1]) ||
1073 ($a[2]<=>$b[2]) ||
1074 ($a[3]<=>$b[3]);
1075 } else {
1076 $entries{$a}->{$qs} cmp $entries{$b}->{$qs};
1077 }
1078 }
1079 }
1080
1081 #Use an associative array (%entries)
1082 foreach my $line (@current2) {
1083 chomp( $line); #remove newline because can be on field 5 or 6 (addition of REMARK)
1084 my @temp = split (',',$line);
1085 my @record = ('FETHER',$temp[0],'FIPADDR',$temp[1],'DATA',join(',',@temp[2..6]));
1086 my $record = {}; # create a reference to empty hash
1087 %{$record} = @record; # populate that hash with @record
1088 # use combination of ether & IP as key to allow duplicates in either but not both
1089 $entries{$record->{FETHER} . $record->{FIPADDR}} = $record; # add this to a hash of hashes
1090 }
1091
1092 open(FILE, ">$filename2") or die 'Unable to open fixed lease file.';
1093 foreach my $entry ( sort fixedleasesort keys %entries) {
1094 print FILE "$entries{$entry}->{FETHER},$entries{$entry}->{FIPADDR},$entries{$entry}->{DATA}\n";
1095 }
1096 close(FILE);
1097
1098 # Reload sorted @current2
1099 open (FILE, "$filename2");
1100 @current2 = <FILE>;
1101 close (FILE);
1102 undef (%entries); #This array is reused latter. Clear it.
1103 }
1104
1105 # Build the configuration file mixing settings, fixed leases and advanced options
1106 sub buildconf {
1107 open(FILE, ">/${General::swroot}/dhcp/dhcpd.conf") or die "Unable to write dhcpd.conf file";
1108 flock(FILE, 2);
1109
1110 # Global settings
1111 print FILE "deny bootp; #default\n";
1112 print FILE "authoritative;\n";
1113
1114 # DNS Update settings
1115 if ($dhcpsettings{'DNS_UPDATE_ENABLED'} eq 'on') {
1116 print FILE "ddns-updates on;\n";
1117 print FILE "ddns-update-style interim;\n";
1118 print FILE "ignore client-updates;\n";
1119 print FILE "update-static-leases on;\n";
1120 } else {
1121 print FILE "ddns-update-style none;\n";
1122 }
1123
1124 # Write first new option definition
1125 foreach my $line (@current1) {
1126 chomp($line); # remove newline
1127 my @temp = split(/\t/,$line);
1128 if (ExistNewOptionDefinition ($temp[1] . ' ' . $temp[2])) {
1129 print FILE "option $temp[1] $temp[2];\n";
1130 }
1131 }
1132 # Write other global options
1133 foreach my $line (@current1) {
1134 chomp($line); # remove newline
1135 my @temp = split(/\t/,$line);
1136
1137 if ($temp[0] eq 'on' && !ExistNewOptionDefinition ($temp[1] . ' ' . $temp[2])){ # active & !definition
1138 my $global=1;
1139 for (my $key=0; $key<@ITFs; $key++) {
1140 my $itf = $temp[3+$key];
1141 if ($itf ne 'off') # Only if an interface name is read
1142 {
1143 $global=0;
1144 }
1145 }
1146 if ($global) {
1147 print FILE "option $temp[1] $temp[2];\n";
1148 }
1149 }# on
1150 }# foreach line
1151
1152 #Subnet range definition
1153 foreach my $itf (@ITFs) {
1154 my $lc_itf=lc($itf);
1155 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on' ){
1156 print FILE "\nsubnet " . $netsettings{"${itf}_NETADDRESS"} . " netmask ". $netsettings{"${itf}_NETMASK"} . " #$itf\n";
1157 print FILE "{\n";
1158 print FILE "\trange " . $dhcpsettings{"START_ADDR_${itf}"} . ' ' . $dhcpsettings{"END_ADDR_${itf}"}.";\n" if ($dhcpsettings{"START_ADDR_${itf}"});
1159 print FILE "\toption subnet-mask " . $netsettings{"${itf}_NETMASK"} . ";\n";
1160 print FILE "\toption domain-name \"" . $dhcpsettings{"DOMAIN_NAME_${itf}"} . "\";\n";
1161 print FILE "\toption routers " . $netsettings{"${itf}_ADDRESS"} . ";\n";
1162 print FILE "\toption domain-name-servers " . $dhcpsettings{"DNS1_${itf}"} if ($dhcpsettings{"DNS1_${itf}"});
1163 print FILE ", " . $dhcpsettings{"DNS2_${itf}"} if ($dhcpsettings{"DNS2_${itf}"});
1164 print FILE ";\n" if ($dhcpsettings{"DNS1_${itf}"});
1165 print FILE "\toption ntp-servers " . $dhcpsettings{"NTP1_${itf}"} if ($dhcpsettings{"NTP1_${itf}"});
1166 print FILE ", " . $dhcpsettings{"NTP2_${itf}"} if ($dhcpsettings{"NTP2_${itf}"});
1167 print FILE ";\n" if ($dhcpsettings{"NTP1_${itf}"});
1168 print FILE "\toption netbios-name-servers " . $dhcpsettings{"WINS1_${itf}"} if ($dhcpsettings{"WINS1_${itf}"});
1169 print FILE ", " . $dhcpsettings{"WINS2_${itf}"} if ($dhcpsettings{"WINS2_${itf}"});
1170 print FILE ";\n" if ($dhcpsettings{"WINS1_${itf}"});
1171 print FILE "\tnext-server " . $dhcpsettings{"NEXT_${itf}"} . ";\n" if ($dhcpsettings{"NEXT_${itf}"});
1172 print FILE "\tfilename \"" . $dhcpsettings{"FILE_${itf}"} . "\";\n" if ($dhcpsettings{"FILE_${itf}"});
1173 print FILE "\tdefault-lease-time " . ($dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} * 60). ";\n";
1174 print FILE "\tmax-lease-time " . ($dhcpsettings{"MAX_LEASE_TIME_${itf}"} * 60) . ";\n";
1175 print FILE "\tallow bootp;\n" if ($dhcpsettings{"ENABLEBOOTP_${itf}"} eq 'on');
1176
1177
1178
1179 # Write scoped options
1180 foreach my $line (@current1) {
1181 chomp($line); # remove newline
1182 my @temp = split(/\t/,$line); # Use TAB separator !
1183
1184 if ($temp[0] eq 'on'){
1185 for (my $key=0; $key<@ITFs; $key++) {
1186 if ($itf eq $temp[3+$key]) # Only is an interface name is read
1187 {
1188 print FILE "\toption $temp[1] $temp[2];\n";
1189 }
1190 }
1191 }# on
1192 }# foreach line
1193 print FILE "} #$itf\n";
1194
1195 if (($dhcpsettings{"DNS_UPDATE_ENABLED"} eq "on") && ($dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} ne "")) {
1196 print FILE "key " . $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} . "{\n";
1197 print FILE "\talgorithm " . $dhcpsettings{"DNS_UPDATE_KEY_ALGO_${itf}"} . ";\n";
1198 print FILE "\tsecret \"" . $dhcpsettings{"DNS_UPDATE_KEY_SECRET_${itf}"} . "\";\n";
1199 print FILE "};\n\n";
1200
1201 print FILE "zone " . $dhcpsettings{"DOMAIN_NAME_${itf}"} . ". {\n";
1202 print FILE "\tkey " . $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} . ";\n";
1203 print FILE "}\n\n";
1204 }
1205
1206 system ('/usr/bin/touch', "${General::swroot}/dhcp/enable_${lc_itf}");
1207 &General::log("DHCP on ${itf}: " . $Lang::tr{'dhcp server enabled'})
1208 } else {
1209 unlink "${General::swroot}/dhcp/enable_${lc_itf}";
1210 &General::log("DHCP on ${itf}: " . $Lang::tr{'dhcp server disabled'})
1211 }
1212 }
1213
1214 #write fixed leases if any. Does not handle duplicates to write them elsewhere than the global scope.
1215 my $key = 0;
1216 foreach my $line (@current2) {
1217 chomp($line);
1218 my @temp = split(/\,/,$line);
1219 if ($temp[2] eq "on") {
1220 print FILE "\nhost fix$key # $temp[6]\n";
1221 print FILE "{\n";
1222 print FILE "\thardware ethernet $temp[0];\n";
1223 print FILE "\tfixed-address $temp[1];\n";
1224 print FILE "\tnext-server $temp[3];\n" if ($temp[3]);
1225 print FILE "\tfilename \"$temp[4]\";\n" if ($temp[4]);
1226 print FILE "\toption root-path \"$temp[5]\";\n" if ($temp[5]);
1227 print FILE "}\n";
1228 $key++;
1229 }
1230 }
1231 print FILE "include \"${General::swroot}/dhcp/dhcpd.conf.local\";\n";
1232 close FILE;
1233 if ( $dhcpsettings{"ENABLE_GREEN"} eq 'on' || $dhcpsettings{"ENABLE_BLUE"} eq 'on' ) {system '/usr/local/bin/dhcpctrl enable >/dev/null 2>&1';}
1234 else {system '/usr/local/bin/dhcpctrl disable >/dev/null 2>&1';}
1235 system '/usr/local/bin/dhcpctrl restart >/dev/null 2>&1';
1236 }
1237
1238 #
1239 # Receive a string and if it match model for a new option,
1240 # add it to the list %newOptions
1241 #
1242 my %NewOptions = ();
1243
1244 sub AddNewOptionDefinition {
1245 my ($line) = @_;
1246 if ( $line =~ /^([-\w]+)( code \d+=($OptionTypes))/ ) {
1247 $NewOptions{$1} = $2;
1248 #&General::log ("new:<$1><$2>");
1249 return 1;
1250 }
1251 return 0;
1252 }
1253
1254 #
1255 # Check existence of definition for a new option
1256 #
1257 sub ExistNewOptionDefinition {
1258 my ($line) = @_;
1259
1260 if ( $line =~ /^([-\w]+)( code \d+=($OptionTypes))/ ) {
1261 return defined $NewOptions{$1};
1262 }
1263 return 0;
1264 }
1265
1266 #
1267 # Check if it is a new option (definition must exist)
1268 # "code=" test eliminate a false response when definition exists
1269 # but this string is a definition with bad $OptionTypes.
1270 sub ValidNewOption {
1271 my ($line) = @_;
1272 if ($line =~ /^([-\w]+) (.*)/ ) {
1273 return defined ( $NewOptions{$1} ) && $2 !~ /code=/;
1274 }
1275 return 0;
1276 }
1277
1278 #
1279 # Check if the new option $opt is used, except the definition of itself!
1280 #
1281 sub IsUsedNewOptionDefinition {
1282 my ($opt,$val) = @_;
1283
1284 foreach my $line (@current1) {
1285 #chomp($line); # remove newline #don't know why, but this remove newline in @current1 .... !
1286 my @temp = split(/\t/,$line);
1287 # if we find something "opt value" & value != "code nnn=" it's ok.
1288 return 1 if ( ($opt eq $temp[1]) && ($temp[2] !~ /code \d+=/) );
1289 }
1290 return 0;
1291 }