2 ###############################################################################
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2007 Michael Tremer & Christian Schmidt #
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. #
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. #
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/>. #
20 ###############################################################################
23 use experimental
'smartmatch';
25 # enable only the following on debugging purpose
27 #use CGI::Carp 'fatalsToBrowser';
29 require '/var/ipfire/general-functions.pl';
30 require "${General::swroot}/lang.pl";
31 require "${General::swroot}/header.pl";
32 #workaround to suppress a warning when a variable is used only once
33 my @dummy = ( ${Header
::colouryellow
} );
40 my $setting = "${General::swroot}/dhcp/settings";
41 our $filename1 = "${General::swroot}/dhcp/advoptions"; # Field separator is TAB in this file (comma is standart)
42 # because we need commas in the some data
43 our $filename2 = "${General::swroot}/dhcp/fixleases";
44 our $filename3 = "${General::swroot}/dhcp/advoptions-list"; # Describe the allowed syntax for dhcp options
45 my $errormessage = '';
46 my $warnNTPmessage = '';
50 #Basic syntax allowed for new Option definition. Not implemented: RECORDS & array of RECORDS
51 our $OptionTypes = 'boolean|((un)?signed )?integer (8|16|32)|ip-address|text|string|encapsulate \w+|array of ip-address';
53 &Header
::showhttpheaders
();
55 if (&Header
::blue_used
()){push(@ITFs,'BLUE');}
57 #Settings1 for the first screen box
58 foreach my $itf (@ITFs) {
59 $dhcpsettings{"ENABLE_${itf}"} = 'off';
60 $dhcpsettings{"ENABLEBOOTP_${itf}"} = 'off';
61 $dhcpsettings{"START_ADDR_${itf}"} = '';
62 $dhcpsettings{"END_ADDR_${itf}"} = '';
63 $dhcpsettings{"DOMAIN_NAME_${itf}"} = '';
64 $dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} = '';
65 $dhcpsettings{"MAX_LEASE_TIME_${itf}"} = '';
66 $dhcpsettings{"WINS1_${itf}"} = '';
67 $dhcpsettings{"WINS2_${itf}"} = '';
68 $dhcpsettings{"DNS1_${itf}"} = '';
69 $dhcpsettings{"DNS2_${itf}"} = '';
70 $dhcpsettings{"NTP1_${itf}"} = '';
71 $dhcpsettings{"NTP2_${itf}"} = '';
72 $dhcpsettings{"NEXT_${itf}"} = '';
73 $dhcpsettings{"FILE_${itf}"} = '';
74 $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} = '';
75 $dhcpsettings{"DNS_UPDATE_KEY_SECRET_${itf}"} = '';
76 $dhcpsettings{"DNS_UPDATE_KEY_ALGO_${itf}"} = '';
77 $dhcpsettings{"DENY_KNOWN_CLIENTS_${itf}"} = 'off';
80 $dhcpsettings{'SORT_FLEASELIST'} = 'FIPADDR';
81 $dhcpsettings{'SORT_LEASELIST'} = 'IPADDR';
84 $dhcpsettings{'DNS_UPDATE_ENABLED'} = 'off';
86 #Settings2 for editing the multi-line list
87 #Must not be saved with writehash !
88 $dhcpsettings{'FIX_MAC'} = '';
89 $dhcpsettings{'FIX_ADDR'} = '';
90 $dhcpsettings{'FIX_ENABLED'} = 'off';
91 $dhcpsettings{'FIX_NEXTADDR'} = '';
92 $dhcpsettings{'FIX_FILENAME'} = '';
93 $dhcpsettings{'FIX_ROOTPATH'} = '';
94 $dhcpsettings{'FIX_REMARK'} = '';
95 $dhcpsettings{'ACTION'} = '';
96 $dhcpsettings{'KEY1'} = '';
97 $dhcpsettings{'KEY2'} = '';
98 @nosaved=('FIX_MAC','FIX_ADDR','FIX_ENABLED','FIX_NEXTADDR',
99 'FIX_FILENAME','FIX_ROOTPATH','FIX_REMARK');
101 $dhcpsettings{'ADVOPT_ENABLED'} = '';
102 $dhcpsettings{'ADVOPT_NAME'} = '';
103 $dhcpsettings{'ADVOPT_DATA'} = '';
104 unshift (@nosaved,'ADVOPT_ENABLED','ADVOPT_NAME','ADVOPT_DATA');
105 foreach my $itf (@ITFs) {
106 $dhcpsettings{"ADVOPT_SCOPE_${itf}"} = 'off';
107 unshift (@nosaved, "ADVOPT_SCOPE_${itf}");
110 # Read Ipcop settings
111 &General
::readhash
("${General::swroot}/ethernet/settings", \
%netsettings);
112 &General
::readhash
("${General::swroot}/main/settings", \
%mainsettings);
113 &General
::readhash
("${General::swroot}/time/settings", \
%timesettings);
114 &General
::readhash
("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \
%color);
117 &Header
::getcgihash
(\
%dhcpsettings);
119 open(FILE
, "$filename1") or die 'Unable to open dhcp advanced options file.';
120 our @current1 = <FILE
>;
122 # Extract OptionDefinition
123 foreach my $line (@current1) {
124 #chomp($line); # remove newline #don't know why, but this remove newline in @current1 .... !
125 my @temp = split(/\t/,$line);
126 AddNewOptionDefinition
($temp[1] . ' ' . $temp[2]);
129 open(FILE
, "$filename2") or die 'Unable to open fixed leases file.';
130 our @current2 = <FILE
>;
133 # Check Settings1 first because they are needed by &buildconf
134 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'save'}) {
135 foreach my $itf (@ITFs) {
136 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on' ) {
137 # "Start" is defined, need "End" and vice versa
138 if ($dhcpsettings{"START_ADDR_${itf}"}) {
139 if (!(&General
::validip
($dhcpsettings{"START_ADDR_${itf}"}))) {
140 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid start address'};
143 if (!$dhcpsettings{"END_ADDR_${itf}"}) {
144 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid end address'};
147 if (! &General
::IpInSubnet
( $dhcpsettings{"START_ADDR_${itf}"},
148 $netsettings{"${itf}_NETADDRESS"},
149 $netsettings{"${itf}_NETMASK"})) {
150 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid start address'};
155 if ($dhcpsettings{"END_ADDR_${itf}"}) {
156 if (!(&General
::validip
($dhcpsettings{"END_ADDR_${itf}"}))) {
157 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid end address'};
160 if (!$dhcpsettings{"START_ADDR_${itf}"}) {
161 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid start address'};
164 if (! &General
::IpInSubnet
( $dhcpsettings{"END_ADDR_${itf}"},
165 $netsettings{"${itf}_NETADDRESS"},
166 $netsettings{"${itf}_NETMASK"})) {
167 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid end address'};
170 #swap if necessary! (support 255.255.0.0 range, I doubt we need more) GE
171 my @startoct = split (/\./, $dhcpsettings{"START_ADDR_${itf}"});
172 my @endoct = split (/\./, $dhcpsettings{"END_ADDR_${itf}"});
173 if ( $endoct[2]*256+$endoct[3] < $startoct[2]*256+$startoct[3] ) {
174 ($dhcpsettings{"START_ADDR_${itf}"},$dhcpsettings{"END_ADDR_${itf}"}) =
175 ($dhcpsettings{"END_ADDR_${itf}"},$dhcpsettings{"START_ADDR_${itf}"});
179 if ($dhcpsettings{"DENY_KNOWN_CLIENTS_${itf}"} eq 'on') {
180 if (($dhcpsettings{"START_ADDR_${itf}"}) eq '' && ($dhcpsettings{"END_ADDR_${itf}"}) eq '') {
181 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'dhcp valid range required when deny known clients checked'};
186 if (!($dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} =~ /^\d+$/)) {
187 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid default lease time'} . $dhcpsettings{'DEFAULT_LEASE_TIME_${itf}'};
191 if (!($dhcpsettings{"MAX_LEASE_TIME_${itf}"} =~ /^\d+$/)) {
192 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid max lease time'} . $dhcpsettings{'MAX_LEASE_TIME_${itf}'};
196 if ($dhcpsettings{"DNS1_${itf}"}) {
197 if (!(&General
::validip
($dhcpsettings{"DNS1_${itf}"}))) {
198 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid primary dns'};
202 if ($dhcpsettings{"DNS2_${itf}"}) {
203 if (!(&General
::validip
($dhcpsettings{"DNS2_${itf}"}))) {
204 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid secondary dns'};
207 if (! $dhcpsettings{"DNS1_${itf}"}) {
208 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'cannot specify secondary dns without specifying primary'};
213 if ($dhcpsettings{"WINS1_${itf}"}) {
214 if (!(&General
::validip
($dhcpsettings{"WINS1_${itf}"}))) {
215 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid wins address'};
219 if ($dhcpsettings{"WINS2_${itf}"}) {
220 if (!(&General
::validip
($dhcpsettings{"WINS2_${itf}"}))) {
221 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid wins address'};
224 if (! $dhcpsettings{"WINS1_${itf}"} ) {
225 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'cannot specify secondary wins without specifying primary'};
229 if ($dhcpsettings{"NEXT_${itf}"}) {
230 if (!(&General
::validip
($dhcpsettings{"NEXT_${itf}"}))) {
231 $errormessage = "next-server on ${itf}: " . $Lang::tr
{'invalid ip'};
235 if ($dhcpsettings{"NTP1_${itf}"}) {
236 if (!(&General
::validip
($dhcpsettings{"NTP1_${itf}"}))) {
237 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid primary ntp'};
240 if ($dhcpsettings{"NTP1_${itf}"} eq $netsettings{"${itf}_ADDRESS"} && ($timesettings{'ENABLECLNTP'} ne 'on')) {
241 $warnNTPmessage = "DHCP on ${itf}: " . $Lang::tr
{'local ntp server specified but not enabled'};
245 if ($dhcpsettings{"NTP2_${itf}"}) {
246 if (!(&General
::validip
($dhcpsettings{"NTP2_${itf}"}))) {
247 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'invalid secondary ntp'};
250 if ($dhcpsettings{"NTP2_${itf}"} eq $netsettings{"${itf}_ADDRESS"} && ($timesettings{'ENABLECLNTP'} ne 'on')) {
251 $warnNTPmessage = "DHCP on ${itf}: " . $Lang::tr
{'local ntp server specified but not enabled'};
254 if (! $dhcpsettings{"NTP1_${itf}"}) {
255 $errormessage = "DHCP on ${itf}: " . $Lang::tr
{'cannot specify secondary ntp without specifying primary'};
260 }#loop interface verify
262 map (delete ($dhcpsettings{$_}) ,@nosaved,'ACTION','KEY1','KEY2','q'); # Must not be saved
263 &General
::writehash
($setting, \
%dhcpsettings); # Save good settings
264 $dhcpsettings{'ACTION'} = $Lang::tr
{'save'}; # create an 'ACTION'
265 map ($dhcpsettings{$_} = '',@nosaved,'KEY1','KEY2'); # and reinit vars to empty
267 ERROR
: # Leave the faulty field untouched
269 &General
::readhash
($setting, \
%dhcpsettings); # Get saved settings and reset to good if needed
272 ## Sorting of fixed leases
273 if ($ENV{'QUERY_STRING'} =~ /^FETHER|^FIPADDR/ ) {
274 my $newsort=$ENV{'QUERY_STRING'};
275 my $act=$dhcpsettings{'SORT_FLEASELIST'};
276 #Reverse actual sort ?
277 if ($act =~ $newsort) {
284 $dhcpsettings{'SORT_FLEASELIST'}=$newsort;
285 map (delete ($dhcpsettings{$_}) ,@nosaved,'ACTION','KEY1','KEY2', 'q'); # Must never be saved
286 &General
::writehash
($setting, \
%dhcpsettings);
288 $dhcpsettings{'ACTION'} = 'SORT'; # create an 'ACTION'
289 map ($dhcpsettings{$_} = '',@nosaved,'KEY1','KEY2');# and reinit vars to empty
292 #Sorting of allocated leases
293 &Header
::CheckSortOrder
;
296 ## Now manipulate the two multi-line list with Settings2.
297 # '1' suffix is for ADVANCED OPTIONS
298 # '2' suffix is for FIXED LEASES
300 # Toggle enable/disable field on specified options.
302 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'toggle enable disable'}.'1') {
304 chomp(@current1[$dhcpsettings{'KEY1'}]);
305 my @temp = split(/\t/,@current1[$dhcpsettings{'KEY1'}]); #use TAB separator !
306 $temp[0] = $temp[0] eq 'on' ?
'' : 'on'; # Toggle the field
307 @current1[$dhcpsettings{'KEY1'}] = join ("\t",@temp)."\n";
308 $dhcpsettings{'KEY1'} = ''; # End edit mode
309 &General
::log($Lang::tr
{'dhcp advopt modified'});
310 open(FILE
, ">$filename1") or die 'Unable to open dhcp advanced options file.';
311 print FILE
@current1;
314 #Write changes to dhcpd.conf.
320 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'add'}.'1' &&
321 $dhcpsettings{'SUBMIT'} ne $Lang::tr
{'dhcp advopt help'}) {
322 $dhcpsettings{'ADVOPT_NAME'} =~ s/[^ \w-]//g; # prevent execution of code by removing everything except letters/space
323 $dhcpsettings{'ADVOPT_DATA'} =~ s/`//g; # back tik ` ? not allowed !
325 if ($dhcpsettings{'ADVOPT_DATA'} eq '') {
326 $errormessage=$Lang::tr
{'dhcp advopt blank value'};
329 # Test for a new option definition string (join field name & data)
330 if (ExistNewOptionDefinition
($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
331 #only edit permitted if option definition exists
332 $errormessage = $Lang::tr
{'dhcp advopt definition exists'} if ($dhcpsettings{'KEY1'} eq '');
333 $dhcpsettings{'ADVOPT_ENABLED'} = 'on'; # force active
334 map ($dhcpsettings{"ADVOPT_SCOPE_$_"} = 'off', @ITFs); # force global
335 } elsif (AddNewOptionDefinition
($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
336 #was a new option definition
337 $dhcpsettings{'ADVOPT_ENABLED'} = 'on'; # force active
338 map ($dhcpsettings{"ADVOPT_SCOPE_$_"} = 'off', @ITFs); # force global
339 } elsif (ValidNewOption
($dhcpsettings{'ADVOPT_NAME'} . ' ' . $dhcpsettings{'ADVOPT_DATA'})) {
341 } elsif (! `grep "\$option $dhcpsettings{'ADVOPT_NAME'} " $filename3`) {
342 $errormessage=$Lang::tr
{'dhcp advopt unknown'}.': '.$dhcpsettings{'ADVOPT_NAME'};
345 unless ($errormessage) {
348 foreach my $itf (@ITFs) { # buils "RED,GREEN,ORANGE,... based on selection
349 $scope .= $dhcpsettings{"ADVOPT_SCOPE_${itf}"} eq 'on' ?
"\t$itf" : "\toff" ;
351 if ($dhcpsettings{'KEY1'} eq '') { #add or edit ? TAB separator !
352 unshift (@current1, "$dhcpsettings{'ADVOPT_ENABLED'}\t$dhcpsettings{'ADVOPT_NAME'}\t$dhcpsettings{'ADVOPT_DATA'}$scope\n");
353 &General
::log($Lang::tr
{'dhcp advopt added'});
355 @current1[$dhcpsettings{'KEY1'}] = "$dhcpsettings{'ADVOPT_ENABLED'}\t$dhcpsettings{'ADVOPT_NAME'}\t$dhcpsettings{'ADVOPT_DATA'}$scope\n";
356 $dhcpsettings{'KEY1'} = ''; # End edit mode
357 &General
::log($Lang::tr
{'dhcp advopt modified'});
360 #Write changes to dhcpd.conf.
361 &sortcurrent1
; # sort newly added/modified entry
362 &buildconf
; # before calling buildconf which use fixed lease file !
366 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'edit'}.'1') {
368 my $line = @current1[$dhcpsettings{'KEY1'}];
370 my @temp = split(/\t/, $line);
371 $dhcpsettings{'ADVOPT_ENABLED'}=$temp[0];
372 $dhcpsettings{'ADVOPT_NAME'}=$temp[1];
373 $dhcpsettings{'ADVOPT_DATA'}=$temp[2];
375 # read next fields which are the name (color) of an interface if this interface is scoped
376 for (my $key=0; $key<@ITFs; $key++) {
377 my $itf = $temp[3+$key];
378 if ($itf ne 'off') # Only is an interface name is read
380 $dhcpsettings{"ADVOPT_SCOPE_${itf}"} = 'on';
385 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'remove'}.'1') {
386 splice (@current1,$dhcpsettings{'KEY1'},1);
387 open(FILE
, ">$filename1") or die 'Unable to open dhcp advanced options file.';
388 print FILE
@current1;
390 $dhcpsettings{'KEY1'} = ''; # End remove mode
391 &General
::log($Lang::tr
{'dhcp advopt removed'});
392 #Write changes to dhcpd.conf.
398 # Toggle enable/disable field on specified lease.
399 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'toggle enable disable'}.'2') {
401 chomp(@current2[$dhcpsettings{'KEY2'}]);
402 my @temp = split(/\,/,@current2[$dhcpsettings{'KEY2'}]);
403 $temp[2] = $temp[2] eq 'on' ?
'' : 'on'; # Toggle the field
404 @current2[$dhcpsettings{'KEY2'}] = join (',',@temp)."\n";
405 $dhcpsettings{'KEY2'} = ''; # End edit mode
406 &General
::log($Lang::tr
{'fixed ip lease modified'});
407 open(FILE
, ">$filename2") or die 'Unable to open fixed leases file.';
408 print FILE
@current2;
411 #Write changes to dhcpd.conf.
415 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'add'}.'2') {
416 $dhcpsettings{'FIX_MAC'} =~ tr/-/:/;
417 unless(&General
::validip
($dhcpsettings{'FIX_ADDR'})) { $errormessage = $Lang::tr
{'invalid fixed ip address'}; }
418 unless(&General
::validmac
($dhcpsettings{'FIX_MAC'})) { $errormessage = $Lang::tr
{'invalid fixed mac address'}; }
419 if ($dhcpsettings{'FIX_NEXTADDR'}) {
420 unless(&General
::validip
($dhcpsettings{'FIX_NEXTADDR'})) { $errormessage = $Lang::tr
{'invalid fixed ip address'}; }
424 CHECK:foreach my $line (@current2) {
425 my @temp = split(/\,/,$line);
426 if($dhcpsettings{'KEY2'} ne $key) {
427 # same MAC is OK on different subnets. This test is not complete because
428 # if ip are not inside a known subnet, I don't warn.
429 # Also it may be needed to put duplicate fixed lease in their right subnet definition..
430 foreach my $itf (@ITFs) {
431 my $scoped = &General
::IpInSubnet
($dhcpsettings{'FIX_ADDR'},
432 $netsettings{"${itf}_NETADDRESS"},
433 $netsettings{"${itf}_NETMASK"}) &&
434 $dhcpsettings{"ENABLE_${itf}"} eq 'on';
436 (lc($dhcpsettings{'FIX_MAC'}) eq lc($temp[0])) &&
437 &General
::IpInSubnet
($temp[1],
438 $netsettings{"${itf}_NETADDRESS"},
439 $netsettings{"${itf}_NETMASK"})) {
440 $errormessage = "$Lang::tr{'mac address in use'} $dhcpsettings{'FIX_MAC'}";
448 unless ($errormessage) {
449 $dhcpsettings{'FIX_REMARK'} = &Header
::cleanhtml
($dhcpsettings{'FIX_REMARK'});
450 $dhcpsettings{'FIX_NEXTADDR'} = &Header
::cleanhtml
($dhcpsettings{'FIX_NEXTADDR'});
451 $dhcpsettings{'FIX_FILENAME'} = &Header
::cleanhtml
($dhcpsettings{'FIX_FILENAME'});
452 $dhcpsettings{'FIX_ROOTPATH'} = &Header
::cleanhtml
($dhcpsettings{'FIX_ROOTPATH'});
453 if ($dhcpsettings{'KEY2'} eq '') { #add or edit ?
454 unshift (@current2, "$dhcpsettings{'FIX_MAC'},$dhcpsettings{'FIX_ADDR'},$dhcpsettings{'FIX_ENABLED'},$dhcpsettings{'FIX_NEXTADDR'},$dhcpsettings{'FIX_FILENAME'},$dhcpsettings{'FIX_ROOTPATH'},$dhcpsettings{'FIX_REMARK'}\n");
455 open(FILE
, ">$filename2") or die 'Unable to open fixed lease file.';
456 print FILE
@current2;
458 &General
::log($Lang::tr
{'fixed ip lease added'});
461 $dhcpsettings{'KEY2'} = 0;
463 @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";
464 $dhcpsettings{'KEY2'} = ''; # End edit mode
465 &General
::log($Lang::tr
{'fixed ip lease modified'});
467 # sort newly added/modified entry
471 #Write changes to dhcpd.conf.
472 &buildconf
; # before calling buildconf which use fixed lease file !
476 if ($dhcpsettings{'ACTION_ALL'} eq '+') {
478 foreach (keys %dhcpsettings) {
479 if (/^(\d+\.\d+\.\d+\.\d+)-([0-9a-fA-F:]+)$/) { # checked names are index of the line
482 if (!grep (/$2/,@current2)) {
483 unshift (@current2, "$mac,$ip,on,,,,imported\n");
489 #Write changes to dhcpd.conf.
490 $warnNTPmessage = $Lang::tr
{'fixed ip lease added'}."($news)";
491 &General
::log($warnNTPmessage);
492 &sortcurrent2
; # sort newly added/modified entry
493 &buildconf
; # before calling buildconf which use fixed lease file !
497 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'edit'}.'2') {
499 my $line = @current2[$dhcpsettings{'KEY2'}];
501 my @temp = split(/\,/, $line);
502 $dhcpsettings{'FIX_MAC'}=$temp[0];
503 $dhcpsettings{'FIX_ADDR'}=$temp[1];
504 $dhcpsettings{'FIX_ENABLED'}=$temp[2];
505 $dhcpsettings{'FIX_NEXTADDR'}=$temp[3];
506 $dhcpsettings{'FIX_FILENAME'}=$temp[4];
507 $dhcpsettings{'FIX_ROOTPATH'}=$temp[5];
508 $dhcpsettings{'FIX_REMARK'}=$temp[6];
511 if ($dhcpsettings{'ACTION'} eq $Lang::tr
{'remove'}.'2') {
512 splice (@current2,$dhcpsettings{'KEY2'},1);
513 open(FILE
, ">$filename2") or die 'Unable to open fixed lease file.';
514 print FILE
@current2;
516 $dhcpsettings{'KEY2'} = ''; # End remove mode
517 &General
::log($Lang::tr
{'fixed ip lease removed'});
518 #Write changes to dhcpd.conf.
526 if ($dhcpsettings{'ACTION'} eq '' ) { # First launch from GUI
528 # Set default DHCP values only if blank and disabled
529 foreach my $itf (@ITFs) {
530 if ($dhcpsettings{"ENABLE_${itf}"} ne 'on' ) {
531 $dhcpsettings{"DNS1_${itf}"} = $netsettings{"${itf}_ADDRESS"};
532 $dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} = '60';
533 $dhcpsettings{"MAX_LEASE_TIME_${itf}"} = '120';
534 $dhcpsettings{"DOMAIN_NAME_${itf}"} = $mainsettings{'DOMAINNAME'};
537 $dhcpsettings{'FIX_ENABLED'} = 'on';
538 $dhcpsettings{'ADVOPT_ENABLED'} = 'on';
541 &Header
::openpage
($Lang::tr
{'dhcp configuration'}, 1, '');
542 &Header
::openbigbox
('100%', 'left', '', $errormessage);
545 &Header
::openbox
('100%', 'left', $Lang::tr
{'error messages'});
546 print "<font class='base'>$errormessage </font>\n";
549 if ($warnNTPmessage) {
550 $warnNTPmessage = "<font color=${Header::colourred}><b>$Lang::tr{'capswarning'}</b></font>: $warnNTPmessage";
553 &Header
::openbox
('100%', 'left', 'DHCP');
554 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>";
556 foreach my $itf (@ITFs) {
558 $checked{'ENABLE'}{'on'} = ( $dhcpsettings{"ENABLE_${itf}"} ne 'on') ?
'' : "checked='checked'";
559 $checked{'ENABLEBOOTP'}{'on'} = ( $dhcpsettings{"ENABLEBOOTP_${itf}"} ne 'on') ?
'' : "checked='checked'";
560 $checked{'DENY_KNOWN_CLIENTS'}{'on'} = ( $dhcpsettings{"DENY_KNOWN_CLIENTS_${itf}"} ne 'on') ?
'' : "checked='checked'";
562 if ($netsettings{"${itf}_DEV"} ne '' ) { # Show only defined interface
567 <td width='25%' class='boldbase'><b><font color='${lc_itf}'>$Lang::tr{"$lc_itf interface"}</font></b></td>
568 <td class='base'>$Lang::tr{'enabled'}
569 <input type='checkbox' name='ENABLE_${itf}' $checked{'ENABLE'}{'on'} /></td>
570 <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>
572 <td width='25%' class='base'>$Lang::tr{'start address'} <img src='/blob.gif' alt='*' /></td>
573 <td width='25%'><input type='text' name='START_ADDR_${itf}' value='$dhcpsettings{"START_ADDR_${itf}"}' /></td>
574 <td width='25%' class='base'>$Lang::tr{'end address'} <img src='/blob.gif' alt='*' /></td>
575 <td width='25%'><input type='text' name='END_ADDR_${itf}' value='$dhcpsettings{"END_ADDR_${itf}"}' /></td>
577 <td class='base'>$Lang::tr{'dhcp deny known clients:'}</td>
578 <td><input type='checkbox' name='DENY_KNOWN_CLIENTS_${itf}' $checked{'DENY_KNOWN_CLIENTS'}{'on'} /></td>
580 <td class='base'>$Lang::tr{'default lease time'} <img src='/blob.gif' alt='*' /></td>
581 <td><input type='text' name='DEFAULT_LEASE_TIME_${itf}' value='$dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"}' /></td>
582 <td class='base'>$Lang::tr{'max lease time'} <img src='/blob.gif' alt='*' /></td>
583 <td><input type='text' name='MAX_LEASE_TIME_${itf}' value='$dhcpsettings{"MAX_LEASE_TIME_${itf}"}' /></td>
585 <td class='base'>$Lang::tr{'domain name suffix'}</td>
586 <td><input type='text' name='DOMAIN_NAME_${itf}' value='$dhcpsettings{"DOMAIN_NAME_${itf}"}' /></td>
587 <td>$Lang::tr{'dhcp allow bootp'}:</td>
588 <td><input type='checkbox' name='ENABLEBOOTP_${itf}' $checked{'ENABLEBOOTP'}{'on'} /></td>
590 <td class='base'>$Lang::tr{'primary dns'} <img src='/blob.gif' alt='*' /></td>
591 <td><input type='text' name='DNS1_${itf}' value='$dhcpsettings{"DNS1_${itf}"}' /></td>
592 <td class='base'>$Lang::tr{'secondary dns'}</td>
593 <td><input type='text' name='DNS2_${itf}' value='$dhcpsettings{"DNS2_${itf}"}' /></td>
595 <td class='base'>$Lang::tr{'primary ntp server'}:</td>
596 <td><input type='text' name='NTP1_${itf}' value='$dhcpsettings{"NTP1_${itf}"}' /></td>
597 <td class='base'>$Lang::tr{'secondary ntp server'}:</td>
598 <td><input type='text' name='NTP2_${itf}' value='$dhcpsettings{"NTP2_${itf}"}' /></td>
600 <td class='base'>$Lang::tr{'primary wins server address'}:</td>
601 <td><input type='text' name='WINS1_${itf}' value='$dhcpsettings{"WINS1_${itf}"}' /></td>
602 <td class='base'>$Lang::tr{'secondary wins server address'}:</td>
603 <td><input type='text' name='WINS2_${itf}' value='$dhcpsettings{"WINS2_${itf}"}' /></td>
605 <td class='base'>next-server:</td>
606 <td><input type='text' name='NEXT_${itf}' value='$dhcpsettings{"NEXT_${itf}"}' /></td>
607 <td class='base'>filename:</td>
608 <td><input type='text' name='FILE_${itf}' value='$dhcpsettings{"FILE_${itf}"}' /></td>
614 }# Show only defined interface
619 <td class='base' width='25%'><img src='/blob.gif' align='top' alt='*' /> $Lang::tr{'required field'}</td>
620 <td class='base' width='30%'>$warnNTPmessage</td>
621 <td width='40%' align='right'><input type='submit' name='ACTION' value='$Lang::tr{'save'}' /></td>
628 # DHCP DNS update support (RFC2136)
629 &Header
::openbox
('100%', 'left', $Lang::tr
{'dhcp dns update'});
632 $checked{'DNS_UPDATE_ENABLED'}{'on'} = ( $dhcpsettings{'DNS_UPDATE_ENABLED'} ne 'on') ?
'' : "checked='checked'";
637 <td width='30%' class='boldbase'>$Lang::tr{'dhcp dns enable update'}</td>
638 <td class='base'><input type='checkbox' name='DNS_UPDATE_ENABLED' $checked{'DNS_UPDATE_ENABLED'}{'on'}>
648 # Print options for each interface.
649 foreach my $itf (@ITFs) {
650 # Check if DHCP for this interface is enabled.
651 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on') {
652 # Check for same domain name.
653 next if ($dhcpsettings{"DOMAIN_NAME_${itf}"} ~~ @domains);
654 my $lc_itf = lc($itf);
656 # Select previously configured update algorithm.
658 $selected{'DNS_UPDATE_ALGO_${inf}'}{$dhcpsettings{'DNS_UPDATE_ALGO_${inf}'}} = 'selected';
662 <td colspan='6'> </td>
665 <td colspan='6' class='boldbase'><b>$dhcpsettings{"DOMAIN_NAME_${itf}"}</b></td>
668 <td width='10%' class='boldbase'>$Lang::tr{'dhcp dns key name'}:</td>
669 <td width='20%'><input type='text' name='DNS_UPDATE_KEY_NAME_${itf}' value='$dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"}'></td>
670 <td width='10%' class='boldbase' align='right'>$Lang::tr{'dhcp dns update secret'}: </td>
671 <td width='20%'><input type='password' name='DNS_UPDATE_KEY_SECRET_${itf}' value='$dhcpsettings{"DNS_UPDATE_KEY_SECRET_${itf}"}'></td>
672 <td width='10%' class='boldbase' align='right'>$Lang::tr{'dhcp dns update algo'}: </td>
674 <select name='DNS_UPDATE_KEY_ALGO_${itf}'>
675 <!-- <option value='hmac-sha1' $selected{'DNS_UPDATE_KEY_ALGO_${itf}'}{'hmac-sha1'}>HMAC-SHA1</option> -->
676 <option value='hmac-md5' $selected{'DNS_UPDATE_KEY_ALGO_${itf}'}{'hmac-md5'}>HMAC-MD5</option>
684 # Store configured domain based on the interface
685 # in the temporary variable.
686 push(@domains, $dhcpsettings{"DOMAIN_NAME_${itf}"});
693 <td align='right'><input type='submit' name='ACTION' value='$Lang::tr{'save'}' /></td>
702 &Header
::openbox
('100%', 'left', $Lang::tr
{'dhcp advopt list'});
703 # DHCP Advanced options settings
705 $checked{'ADVOPT_ENABLED'}{'on'} = ($dhcpsettings{'ADVOPT_ENABLED'} ne 'on') ?
'' : "checked='checked'";
707 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'><table width='100%'>";
708 my $buttontext = $Lang::tr
{'add'};
709 if ($dhcpsettings{'KEY1'} ne '') {
710 $buttontext = $Lang::tr
{'update'};
711 print "<tr><td class='boldbase'><b>$Lang::tr{'dhcp advopt edit'}</b></td></tr>";
713 print "<tr><td class='boldbase'><b>$Lang::tr{'dhcp advopt add'}</b></td></tr>"
716 #search if the 'option' is in the list and print the syntax model
717 my $opt = `grep "\$option $dhcpsettings{'ADVOPT_NAME'} " $filename3`;
719 $opt =~ s/option $dhcpsettings{'ADVOPT_NAME'}/Syntax:/; # "option xyz abc" => "syntax: abc"
721 $opt = "<tr><td></td><td></td><td colspan='2'>$opt</td></tr>";
725 <td class='base'>$Lang::tr{'dhcp advopt name'}: <img src='/blob.gif' alt='*' /></td>
726 <td><input type='text' name='ADVOPT_NAME' value='$dhcpsettings{'ADVOPT_NAME'}' size='18' /></td>
727 <td class='base'>$Lang::tr{'dhcp advopt value'}: <img src='/blob.gif' alt='*' /></td>
728 <td><input type='text' name='ADVOPT_DATA' value='$dhcpsettings{'ADVOPT_DATA'}' size='40' /></td>
730 <td class='base'>$Lang::tr{'enabled'}</td><td><input type='checkbox' name='ADVOPT_ENABLED' $checked{'ADVOPT_ENABLED'}{'on'} /></td>
731 <td class='base'>$Lang::tr{'dhcp advopt scope'}:</td>
736 # Put a checkbox for each interface. Checkbox visible disabled if interface is disabled
737 foreach my $itf (@ITFs) {
739 $checked{'ADVOPT_SCOPE_${itf}'}{'on'} = $dhcpsettings{"ADVOPT_SCOPE_${itf}"} ne 'on' ?
'' : "checked='checked'";
740 print "$Lang::tr{\"${lc_itf}\"} <input type='checkbox' name='ADVOPT_SCOPE_${itf}' $checked{'ADVOPT_SCOPE_${itf}'}{'on'} ";
741 print $dhcpsettings{"ENABLE_${itf}"} eq 'on' ?
"/>" : "disabled='disabled' />";
742 print " ";
752 <td class='base' width='50%'>$Lang::tr{'dhcp advopt scope help'}</td>
753 <td width='50%' align='right'>
754 <input type='hidden' name='ACTION' value='$Lang::tr{'add'}1' />
755 <input type='submit' name='SUBMIT' value='$buttontext' />
756 <input type='submit' name='SUBMIT' value='$Lang::tr{'dhcp advopt help'}' />
757 <input type='hidden' name='KEY1' value='$dhcpsettings{'KEY1'}' />
764 #Edited line number (KEY1) passed until cleared by 'save' or 'remove' or 'new sort order'
766 # print help taken from the file describing options
767 if ($dhcpsettings{'SUBMIT'} eq $Lang::tr
{'dhcp advopt help'}) {
769 print "<table width='100%'>";
770 print "<tr><td width='30%'><b>$Lang::tr{'dhcp advopt name'}</b></td><td width='70%'><b>$Lang::tr{'dhcp advopt value'}</b></td>";
771 open(FILE
, "$filename3");
772 my @current3 = <FILE
>;
774 foreach my $line (@current3) {
775 $line =~ /option ([a-z0-9-]+) (.*);/;
776 print "<tr><td>$1</td><td>$2</td></tr>\n";
778 print "<tr><td colspan='2'><hr /></td></tr>\n";
779 print '<tr><td>string type</td><td>"quoted string" or 00:01:FF...</td></tr>';
780 print '<tr><td>ip-address type </td><td>10.0.0.1 | www.dot.com</td></tr>';
781 print '<tr><td>int,uint types</td><td>numbers</td></tr>';
782 print '<tr><td>flag type</td><td>on | off</td></tr>';
785 print "<table width='100%'>";
786 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>";
787 print "<tr><td>any-name </td><td> code NNN=$OptionTypes</td></tr>";
788 print '<tr><td>a-string</td><td>code 100=string</td></tr>';
789 print '<tr><td>a-number</td><td>code 101=signed integer 8</td></tr>';
790 print '<tr><td>wpad</td><td>code 252=text</td></tr>';
791 print '<tr><td>wpad</td><td>"http://www.server.fr/path-to/proxy.pac"</td></tr>';
800 <td width='30%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt name'}</b></td>
801 <td width='50%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt value'}</b></td>
802 <td width='20%' class='boldbase' align='center'><b>$Lang::tr{'dhcp advopt scope'}</b></td>
803 <td colspan='3' class='boldbase' align='center'><b>$Lang::tr{'action'}</b></td>
808 foreach my $line (@current1) {
811 chomp($line); # remove newline
812 my @temp = split(/\t/,$line);
814 if ($temp[0] eq "on") {
816 $gdesc = $Lang::tr
{'click to disable'};
819 $gdesc = $Lang::tr
{'click to enable'};
822 if ($dhcpsettings{'KEY1'} eq $key) {
823 print "<tr bgcolor='${Header::colouryellow}'>";
825 print "<tr bgcolor='$color{'color22'}'>";
827 print "<tr bgcolor='$color{'color20'}'>";
831 <td align='center'>$temp[1]</td>
832 <td align='center'>$temp[2]</td>
836 # Prepare a global flag to make easy reading
838 my $disabledTogle = '';
839 my $disabledEditRemove = '';
840 if ( ExistNewOptionDefinition
($temp[1] . ' ' . $temp[2]) ) {
841 $global = $Lang::tr
{'dhcp advopt definition'};
842 $disabledTogle = "disabled='disabled'";
843 # Search if it is a used NewOptionDefinition to also disable edit & delete
844 $disabledEditRemove = "disabled='disabled'" if (IsUsedNewOptionDefinition
($temp[1], $temp[2]));
846 $global = $Lang::tr
{'dhcp advopt scope global'};
850 # Print each checked interface
851 for (my $key=0; $key<@ITFs; $key++) {
852 my $itf = $temp[3+$key];
853 if ($itf ne 'off') { # Only if an interface name is read
855 $global=''; # fall to local scope !
861 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
862 <input type='hidden' name='ACTION' value='$Lang::tr{'toggle enable disable'}1' />
863 <input $disabledTogle type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc' />
864 <input type='hidden' name='KEY1' value='$key' />
869 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
870 <input type='hidden' name='ACTION' value='$Lang::tr{'edit'}1' />
871 <input $disabledEditRemove type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}' />
872 <input type='hidden' name='KEY1' value='$key' />
877 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
878 <input type='hidden' name='ACTION' value='$Lang::tr{'remove'}1' />
879 <input $disabledEditRemove type='image' name='$Lang::tr{'remove'}' src='/images/delete.gif' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' />
880 <input type='hidden' name='KEY1' value='$key' />
891 # If there are dhcp options, print Key to action icons
896 <td class='boldbase'> <b>$Lang::tr{'legend'}: </b></td>
897 <td><img src='/images/on.gif' alt='$Lang::tr{'click to disable'}' /></td>
898 <td class='base'>$Lang::tr{'click to disable'}</td>
899 <td> </td>
900 <td><img src='/images/off.gif' alt='$Lang::tr{'click to enable'}' /></td>
901 <td class='base'>$Lang::tr{'click to enable'}</td>
902 <td> </td>
903 <td><img src='/images/edit.gif' alt='$Lang::tr{'edit'}' /></td>
904 <td class='base'>$Lang::tr{'edit'}</td>
905 <td> </td>
906 <td><img src='/images/delete.gif' alt='$Lang::tr{'remove'}' /></td>
907 <td class='base'>$Lang::tr{'remove'}</td>
915 &Header
::openbox
('100%', 'left', $Lang::tr
{'current fixed leases'});
916 # Fixed leases screens
917 $checked{'FIX_ENABLED'}{'on'} = ($dhcpsettings{'FIX_ENABLED'} ne 'on') ?
'' : "checked='checked'";
919 $buttontext = $Lang::tr
{'add'};
920 print "<form method='post' action='$ENV{'SCRIPT_NAME'}'><table width='100%'>";
922 if ($dhcpsettings{'KEY2'} ne '') {
923 $buttontext = $Lang::tr
{'update'};
924 print "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'edit an existing lease'}</b></td></tr>";
926 print "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'add new lease'}</b></td></tr>"
930 <td class='base'>$Lang::tr{'mac address'}: <img src='/blob.gif' alt='*' /></td>
931 <td><input type='text' name='FIX_MAC' value='$dhcpsettings{'FIX_MAC'}' size='18' /></td>
932 <td class='base'>$Lang::tr{'ip address'}: <img src='/blob.gif' alt='*' /></td>
933 <td><input type='text' name='FIX_ADDR' value='$dhcpsettings{'FIX_ADDR'}' size='18' /></td>
934 <td class='base'>$Lang::tr{'remark'}:</td>
935 <td><input type='text' name='FIX_REMARK' value='$dhcpsettings{'FIX_REMARK'}' size='18' /></td>
937 <td class='base'>$Lang::tr{'enabled'}</td><td><input type='checkbox' name='FIX_ENABLED' $checked{'FIX_ENABLED'}{'on'} /></td>
939 <td colspan = '3'><b>$Lang::tr{'dhcp bootp pxe data'}</b></td>
941 <td class='base'>next-server:</td>
942 <td><input type='text' name='FIX_NEXTADDR' value='$dhcpsettings{'FIX_NEXTADDR'}' size='18' /></td>
943 <td class='base'>filename:</td>
944 <td><input type='text' name='FIX_FILENAME' value='$dhcpsettings{'FIX_FILENAME'}' size='18' /></td>
945 <td class='base'>root path:</td>
946 <td><input type='text' name='FIX_ROOTPATH' value='$dhcpsettings{'FIX_ROOTPATH'}' size='18' /></td>
952 <td class='base' width='50%'><img src='/blob.gif' align='top' alt='*' /> $Lang::tr{'required field'}</td>
953 <td width='50%' align='right'>
954 <input type='hidden' name='ACTION' value='$Lang::tr{'add'}2' />
955 <input type='submit' name='SUBMIT' value='$buttontext' />
956 <input type='hidden' name='KEY2' value='$dhcpsettings{'KEY2'}' /></td>
962 #Edited line number (KEY2) passed until cleared by 'save' or 'remove' or 'new sort order'
964 # Search for static leases
965 my $search_query = $dhcpsettings{'q'};
967 if (scalar @current2 >= 10) {
969 <form method="POST" action="#search">
970 <a name="search"></a>
974 <input type="text" name="q" value="$search_query">
975 <input type="submit" value="$Lang::tr{'search'}">
984 <table width='100%' class='tbl'>
986 <th width='20%' align='center'><a href='$ENV{'SCRIPT_NAME'}?FETHER'><b>$Lang::tr{'mac address'}</b></a></th>
987 <th width='20%' align='center'><a href='$ENV{'SCRIPT_NAME'}?FIPADDR'><b>$Lang::tr{'ip address'}</b></a></th>
988 <th width='15%' align='center'><b>$Lang::tr{'remark'}</b></th>
989 <th width='15%' class='boldbase' align='center'><b>next-server</b></th>
990 <th width='15%' class='boldbase' align='center'><b>filename</b></th>
991 <th width='15%' class='boldbase' align='center'><b>root path</b></th>
992 <th colspan='3' class='boldbase' align='center'><b>$Lang::tr{'action'}</b></th>
998 my %macdupl = (); # Duplicate MACs have to be on different subnets
1001 # mark duplicate ip or duplicate MAC
1002 foreach my $line (@current2) {
1003 my @temp = split(/\,/,$line);
1004 $macdupl{$temp[0]} += 1;
1005 if ($macdupl{$temp[0]} > 1) {
1006 $ipdup = 1; # Flag up duplicates for use later
1008 $ipinuse{$temp[1]} += 1;
1009 if ($ipinuse{$temp[1]} > 1) {
1010 $ipdup = 1; # Flag up duplicates for use later
1012 # Mark IP addresses outwith known subnets
1013 $ipoutside{$temp[1]} = 1;
1014 foreach my $itf (@ITFs) {
1015 if ( &General
::IpInSubnet
($temp[1],
1016 $netsettings{"${itf}_NETADDRESS"},
1017 $netsettings{"${itf}_NETMASK"})) {
1018 $ipoutside{$temp[1]} = 0;
1025 foreach my $line (@current2) {
1028 chomp($line); # remove newline
1029 my @temp = split(/\,/,$line);
1031 if ($temp[2] eq "on") {
1033 $gdesc = $Lang::tr
{'click to disable'};
1036 $gdesc = $Lang::tr
{'click to enable'};
1039 # Skip all entries that do not match the search query
1040 if ($search_query ne "") {
1041 if (!grep(/$search_query/, @temp)) {
1047 if ($dhcpsettings{'KEY2'} eq $key) {
1049 $col="bgcolor='${Header::colouryellow}'";
1050 } elsif ($key % 2) {
1052 $col="bgcolor='$color{'color20'}'";
1055 $col="bgcolor='$color{'color22'}'";
1062 if ($ipinuse{$temp[1]} > 1) {
1066 if ($macdupl{$temp[0]} > 1) {
1070 if ($ipoutside{$temp[1]} > 0) {
1071 $TAG4 = "bgcolor='orange'" if ($dhcpsettings{'KEY2'} ne $key);
1075 <td align='center' $col>$TAG2$temp[0]$TAG3</td>
1076 <td align='center' $col $TAG4>$TAG0$temp[1]$TAG1</td>
1077 <td align='center' $col>$temp[6] </td>
1078 <td align='center' $col>$temp[3] </td>
1079 <td align='center' $col>$temp[4] </td>
1080 <td align='center' $col>$temp[5] </td>
1082 <td align='center' $col>
1083 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
1084 <input type='hidden' name='ACTION' value='$Lang::tr{'toggle enable disable'}2' />
1085 <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc' />
1086 <input type='hidden' name='KEY2' value='$key' />
1090 <td align='center' $col>
1091 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
1092 <input type='hidden' name='ACTION' value='$Lang::tr{'edit'}2' />
1093 <input type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}' />
1094 <input type='hidden' name='KEY2' value='$key' />
1098 <td align='center' $col>
1099 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
1100 <input type='hidden' name='ACTION' value='$Lang::tr{'remove'}2' />
1101 <input type='image' name='$Lang::tr{'remove'}' src='/images/delete.gif' alt='$Lang::tr{'remove'}' title='$Lang::tr{'remove'}' />
1102 <input type='hidden' name='KEY2' value='$key' />
1112 # If the fixed lease file contains entries, print Key to action icons
1114 my $dup = $ipdup ?
"<td class='base'>$Lang::tr{'duplicate ip bold'}</td>" :'';
1118 <td class='boldbase'> <b>$Lang::tr{'legend'}: </b></td>
1119 <td><img src='/images/on.gif' alt='$Lang::tr{'click to disable'}' /></td>
1120 <td class='base'>$Lang::tr{'click to disable'}</td>
1121 <td> </td>
1122 <td><img src='/images/off.gif' alt='$Lang::tr{'click to enable'}' /></td>
1123 <td class='base'>$Lang::tr{'click to enable'}</td>
1124 <td> </td>
1125 <td><img src='/images/edit.gif' alt='$Lang::tr{'edit'}' /></td>
1126 <td class='base'>$Lang::tr{'edit'}</td>
1127 <td> </td>
1128 <td><img src='/images/delete.gif' alt='$Lang::tr{'remove'}' /></td>
1129 <td class='base'>$Lang::tr{'remove'}</td>
1133 <td bgcolor='orange'> </td>
1134 <td class='base'>$Lang::tr{'ip address outside subnets'}</td>
1144 &Header
::closebox
();
1146 foreach my $itf (@ITFs) {
1147 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on') {
1148 # display leases with a list of actions to do with the global select checkbox.
1149 &Header
::PrintActualLeases
("+"); # "+" => create fixed leases from nodeaddress
1150 last; #Print one time only for all interfaces
1154 &Header
::closebigbox
();
1155 &Header
::closepage
();
1157 ## Ouf it's the end !
1159 sub sortcurrent1
# by now, do not sort, just write
1161 open(FILE
, ">$filename1") or die 'Unable to open dhcp advanced options file.';
1162 print FILE
@current1;
1167 # Sort the "current2" array according to choices
1172 sub fixedleasesort
{
1174 if (rindex ($dhcpsettings{'SORT_FLEASELIST'},'Rev') != -1) {
1175 $qs=substr ($dhcpsettings{'SORT_FLEASELIST'},0,length($dhcpsettings{'SORT_FLEASELIST'})-3);
1176 if ($qs eq 'FIPADDR') {
1177 my @a = split(/\./,$entries{$a}->{$qs});
1178 my @b = split(/\./,$entries{$b}->{$qs});
1184 $entries{$b}->{$qs} cmp $entries{$a}->{$qs};
1186 } else { #not reverse
1187 $qs=$dhcpsettings{'SORT_FLEASELIST'};
1188 if ($qs eq 'FIPADDR') {
1189 my @a = split(/\./,$entries{$a}->{$qs});
1190 my @b = split(/\./,$entries{$b}->{$qs});
1196 $entries{$a}->{$qs} cmp $entries{$b}->{$qs};
1201 #Use an associative array (%entries)
1202 foreach my $line (@current2) {
1203 chomp( $line); #remove newline because can be on field 5 or 6 (addition of REMARK)
1204 my @temp = split (',',$line);
1205 my @record = ('FETHER',$temp[0],'FIPADDR',$temp[1],'DATA',join(',',@temp[2..6]));
1206 my $record = {}; # create a reference to empty hash
1207 %{$record} = @record; # populate that hash with @record
1208 # use combination of ether & IP as key to allow duplicates in either but not both
1209 $entries{$record->{FETHER
} . $record->{FIPADDR
}} = $record; # add this to a hash of hashes
1212 open(FILE
, ">$filename2") or die 'Unable to open fixed lease file.';
1213 foreach my $entry ( sort fixedleasesort
keys %entries) {
1214 print FILE
"$entries{$entry}->{FETHER},$entries{$entry}->{FIPADDR},$entries{$entry}->{DATA}\n";
1218 # Reload sorted @current2
1219 open (FILE
, "$filename2");
1222 undef (%entries); #This array is reused latter. Clear it.
1225 # Build the configuration file mixing settings, fixed leases and advanced options
1227 open(FILE
, ">/${General::swroot}/dhcp/dhcpd.conf") or die "Unable to write dhcpd.conf file";
1231 print FILE
"deny bootp; #default\n";
1232 print FILE
"authoritative;\n";
1234 # DNS Update settings
1235 if ($dhcpsettings{'DNS_UPDATE_ENABLED'} eq 'on') {
1236 print FILE
"ddns-updates on;\n";
1237 print FILE
"ddns-update-style interim;\n";
1238 print FILE
"ddns-ttl 60; # 1 min\n";
1239 print FILE
"ignore client-updates;\n";
1240 print FILE
"update-static-leases on;\n";
1242 print FILE
"ddns-update-style none;\n";
1245 # Write first new option definition
1246 foreach my $line (@current1) {
1247 chomp($line); # remove newline
1248 my @temp = split(/\t/,$line);
1249 if (ExistNewOptionDefinition
($temp[1] . ' ' . $temp[2])) {
1250 print FILE
"option $temp[1] $temp[2];\n";
1253 # Write other global options
1254 foreach my $line (@current1) {
1255 chomp($line); # remove newline
1256 my @temp = split(/\t/,$line);
1258 if ($temp[0] eq 'on' && !ExistNewOptionDefinition
($temp[1] . ' ' . $temp[2])){ # active & !definition
1260 for (my $key=0; $key<@ITFs; $key++) {
1261 my $itf = $temp[3+$key];
1262 if ($itf ne 'off') # Only if an interface name is read
1268 print FILE
"option $temp[1] $temp[2];\n";
1274 #Subnet range definition
1275 foreach my $itf (@ITFs) {
1276 my $lc_itf=lc($itf);
1277 if ($dhcpsettings{"ENABLE_${itf}"} eq 'on' ){
1278 print FILE
"subnet " . $netsettings{"${itf}_NETADDRESS"} . " netmask ". $netsettings{"${itf}_NETMASK"} . " #$itf\n";
1280 if ($dhcpsettings{"START_ADDR_${itf}"}) {
1281 print FILE
"pool {\n";
1282 print FILE
"\trange " . $dhcpsettings{"START_ADDR_${itf}"} . ' ' . $dhcpsettings{"END_ADDR_${itf}"}.";\n";
1283 print FILE
"\tdeny known-clients;\n" if ($dhcpsettings{"DENY_KNOWN_CLIENTS_${itf}"} eq 'on');
1284 print FILE
" }\n"; # pool
1286 print FILE
"\toption subnet-mask " . $netsettings{"${itf}_NETMASK"} . ";\n";
1287 print FILE
"\toption domain-name \"" . $dhcpsettings{"DOMAIN_NAME_${itf}"} . "\";\n";
1288 print FILE
"\toption routers " . $netsettings{"${itf}_ADDRESS"} . ";\n";
1289 print FILE
"\toption domain-name-servers " . $dhcpsettings{"DNS1_${itf}"} if ($dhcpsettings{"DNS1_${itf}"});
1290 print FILE
", " . $dhcpsettings{"DNS2_${itf}"} if ($dhcpsettings{"DNS2_${itf}"});
1291 print FILE
";\n" if ($dhcpsettings{"DNS1_${itf}"});
1292 print FILE
"\toption ntp-servers " . $dhcpsettings{"NTP1_${itf}"} if ($dhcpsettings{"NTP1_${itf}"});
1293 print FILE
", " . $dhcpsettings{"NTP2_${itf}"} if ($dhcpsettings{"NTP2_${itf}"});
1294 print FILE
";\n" if ($dhcpsettings{"NTP1_${itf}"});
1295 print FILE
"\toption netbios-name-servers " . $dhcpsettings{"WINS1_${itf}"} if ($dhcpsettings{"WINS1_${itf}"});
1296 print FILE
", " . $dhcpsettings{"WINS2_${itf}"} if ($dhcpsettings{"WINS2_${itf}"});
1297 print FILE
";\n" if ($dhcpsettings{"WINS1_${itf}"});
1298 print FILE
"\tnext-server " . $dhcpsettings{"NEXT_${itf}"} . ";\n" if ($dhcpsettings{"NEXT_${itf}"});
1299 print FILE
"\tfilename \"" . &EscapeFilename
($dhcpsettings{"FILE_${itf}"}) . "\";\n" if ($dhcpsettings{"FILE_${itf}"});
1300 print FILE
"\tdefault-lease-time " . ($dhcpsettings{"DEFAULT_LEASE_TIME_${itf}"} * 60). ";\n";
1301 print FILE
"\tmax-lease-time " . ($dhcpsettings{"MAX_LEASE_TIME_${itf}"} * 60) . ";\n";
1302 print FILE
"\tallow bootp;\n" if ($dhcpsettings{"ENABLEBOOTP_${itf}"} eq 'on');
1306 # Write scoped options
1307 foreach my $line (@current1) {
1308 chomp($line); # remove newline
1309 my @temp = split(/\t/,$line); # Use TAB separator !
1311 if ($temp[0] eq 'on'){
1312 for (my $key=0; $key<@ITFs; $key++) {
1313 if ($itf eq $temp[3+$key]) # Only is an interface name is read
1315 print FILE
"\toption $temp[1] $temp[2];\n";
1320 print FILE
"} #$itf\n\n";
1322 if (($dhcpsettings{"DNS_UPDATE_ENABLED"} eq "on") && ($dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} ne "")) {
1323 print FILE
"key " . $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} . " {\n";
1324 print FILE
"\talgorithm " . $dhcpsettings{"DNS_UPDATE_KEY_ALGO_${itf}"} . ";\n";
1325 print FILE
"\tsecret \"" . $dhcpsettings{"DNS_UPDATE_KEY_SECRET_${itf}"} . "\";\n";
1326 print FILE
"};\n\n";
1328 print FILE
"zone " . $dhcpsettings{"DOMAIN_NAME_${itf}"} . ". {\n";
1329 print FILE
"\tkey " . $dhcpsettings{"DNS_UPDATE_KEY_NAME_${itf}"} . ";\n";
1333 &General
::system('/usr/bin/touch', "${General::swroot}/dhcp/enable_${lc_itf}");
1334 &General
::log("DHCP on ${itf}: " . $Lang::tr
{'dhcp server enabled'})
1336 unlink "${General::swroot}/dhcp/enable_${lc_itf}";
1337 &General
::log("DHCP on ${itf}: " . $Lang::tr
{'dhcp server disabled'})
1341 #write fixed leases if any. Does not handle duplicates to write them elsewhere than the global scope.
1343 foreach my $line (@current2) {
1345 my @temp = split(/\,/,$line);
1346 if ($temp[2] eq "on") {
1347 print FILE
"\nhost fix$key # $temp[6]\n";
1349 print FILE
"\thardware ethernet $temp[0];\n";
1350 print FILE
"\tfixed-address $temp[1];\n";
1351 print FILE
"\tnext-server $temp[3];\n" if ($temp[3]);
1352 print FILE
"\tfilename \"" . &EscapeFilename
($temp[4]) . "\";\n" if ($temp[4]);
1353 print FILE
"\toption root-path \"$temp[5]\";\n" if ($temp[5]);
1358 print FILE
"include \"${General::swroot}/dhcp/dhcpd.conf.local\";\n";
1360 if ( $dhcpsettings{"ENABLE_GREEN"} eq 'on' || $dhcpsettings{"ENABLE_BLUE"} eq 'on' ) {&General
::system('/usr/local/bin/dhcpctrl', 'enable');}
1361 else {&General
::system('/usr/local/bin/dhcpctrl', 'disable');}
1362 &General
::system_background
('/usr/local/bin/dhcpctrl', 'restart');
1366 # Receive a string and if it match model for a new option,
1367 # add it to the list %newOptions
1369 my %NewOptions = ();
1371 sub AddNewOptionDefinition
{
1373 if ( $line =~ /^([-\w]+)( code \d+=($OptionTypes))/ ) {
1374 $NewOptions{$1} = $2;
1375 #&General::log ("new:<$1><$2>");
1382 # Check existence of definition for a new option
1384 sub ExistNewOptionDefinition
{
1387 if ( $line =~ /^([-\w]+)( code \d+=($OptionTypes))/ ) {
1388 return defined $NewOptions{$1};
1394 # Check if it is a new option (definition must exist)
1395 # "code=" test eliminate a false response when definition exists
1396 # but this string is a definition with bad $OptionTypes.
1397 sub ValidNewOption
{
1399 if ($line =~ /^([-\w]+) (.*)/ ) {
1400 return defined ( $NewOptions{$1} ) && $2 !~ /code=/;
1406 # Check if the new option $opt is used, except the definition of itself!
1408 sub IsUsedNewOptionDefinition
{
1409 my ($opt,$val) = @_;
1411 foreach my $line (@current1) {
1412 #chomp($line); # remove newline #don't know why, but this remove newline in @current1 .... !
1413 my @temp = split(/\t/,$line);
1414 # if we find something "opt value" & value != "code nnn=" it's ok.
1415 return 1 if ( ($opt eq $temp[1]) && ($temp[2] !~ /code \d+=/) );
1420 sub EscapeFilename
($) {
1421 my $filename = shift;
1423 # Replace all single / by \/
1424 $filename =~ s/\//\\\
//g;