2 # Functions for parsing the DHCPD config file
4 BEGIN { push(@INC, ".."); };
8 # get_parent_config([file])
9 # Returns a dummy parent structure for the DHCP config
13 $file ||= $config{'dhcpd_conf'};
14 return $get_parent_config_cache{$file} if ($get_parent_config_cache{$file});
15 return $get_parent_config_cache{$file} = {
17 'members' => &get_config($file),
20 'eline' => $get_config_lines };
24 # Parses the DHCPD config file into a data structure
28 $file ||= $config{'dhcpd_conf'};
29 return $get_config_cache{$file} if ($get_config_cache{$file});
30 local @rv = &get_config_file($file, \$get_config_lines);
31 $get_config_cache{$file} = \@rv;
32 return $get_config_cache{$file};
35 # get_config_file(file, [&lines])
38 local (@tok, $i, $j, @rv, $c);
40 local $lines = &tokenize_file($_[0], \@tok);
41 ${$_[1]} = $lines if ($_[1]);
43 local $str = &parse_struct(\@tok, \$i, $j++, $_[0]);
45 if ($str->{'name'} eq 'include') {
46 # Expand the include directive
47 local $p = $str->{'values'}->[0];
49 $config{'dhcpd_conf'} =~ /^(\S+)\//;
52 local @inc = &get_config_file($p);
68 # tokenize_file(file, &tokens)
72 local ($line, $cmode);
74 while($line = <FILE>) {
77 $line =~ s/^([^"#]*)#.*$/$1/g;
78 $line =~ s/^([^"]*)\/\/.*$/$1/g;
79 $line =~ s/^([^"]*)\s+#.*$/$1/g; # remove stuff after #, unless
80 $line =~ s/^(.*".*".*)\s+#.*$/$1/g; # it is inside quotes
81 $line =~ s/\\\\/\\134/g; # convert \\ into \134
82 $line =~ s/([^\\])\\"/$1\\042/g; # convert escaped quotes to \042
84 if (!$cmode && $line =~ /\/\*/ && $line !~ /\".*\/\*.*\"/) {
85 # start of a C-style comment
87 $line =~ s/\/\*.*$//g;
90 if ($line =~ /\*\//) {
93 $line =~ s/^.*\*\///g;
95 else { $line = ""; last; }
100 # split line into tokens
102 if ($line =~ /^\s*"([^"]*)"(.*)$/) {
103 push(@{$_[1]}, [ $1, 1, $lines ]); $line = $2;
105 elsif ($line =~ /^\s*([{};,])(.*)$/) {
106 push(@{$_[1]}, [ $1, 0, $lines ]); $line = $2;
108 elsif ($line =~ /^\s*([^{}; \t,]+)(.*)$/) {
109 push(@{$_[1]}, [ $1, 0, $lines ]); $line = $2;
116 #print STDERR "tokenized $_[0] into $lines\n";
120 # parse_struct(&tokens, &token_num, index, file)
121 # A structure can either have one value, or a list of values.
122 # Pos will end up at the start of the next structure
125 local(%str, $i, $t, @vals, @quotes, $str, @text);
126 local $lref = &read_file_lines($_[3]);
128 $str{'name'} = $_[0]->[$i]->[0];
129 $str{'line'} = $_[0]->[$i]->[2];
130 if ($str{'line'} && $lref->[$str{'line'}-1] =~ /^\s*(#|\/\/)\s*(.*)/) {
131 # Previous line is a comment, so include it in the directive
133 $str{'comment'} = $2;
135 #print STDERR "parsing at line $str{'line'} = $str{'name'}\n";
137 # Add values between directive name and { or ;
139 if (($t->[0] eq "{" && $str{'name'} ne 'option') ||
140 $t->[0] eq ";") { last; }
141 elsif (!defined($t->[0])) { ${$_[1]} = $i; return undef; }
142 else { push(@vals, $t->[0]); push(@quotes, $t->[1]); }
143 push(@text, $t->[1] ? "\"$t->[0]\"" : $t->[0]);
145 $str{'values'} = \@vals;
146 $str{'quotes'} = \@quotes;
147 $str{'value'} = $vals[0];
148 $str{'text'} = join(" ", @text);
149 $str{'index'} = $_[2];
150 $str{'file'} = $_[3];
151 $str{'fline'} = $_[0]->[$i]->[2];
152 if ($t->[0] eq "{") {
153 # contains sub-structures.. parse them
158 while($_[0]->[$i]->[0] ne "}") {
159 if (!defined($_[0]->[$i]->[0]))
160 { ${$_[1]} = $i; return undef; }
161 $str = &parse_struct($_[0], \$i, $j++, $_[3]);
163 $str->{'parent'} = \%str;
167 $str{'members'} = \@mems;
168 $i++; # skip trailing }
171 # only a single value..
173 $i++; # skip trailing ;
175 $str{'eline'} = $_[0]->[$i-1]->[2]; # ending line is the line number the
185 foreach $c (@{$_[1]}) {
186 if ($c->{'name'} eq $_[0]) {
190 return @rv ? wantarray ? @rv : $rv[0]
191 : wantarray ? () : undef;
194 # find_value(name, &array)
198 @v = &find($_[0], $_[1]);
199 if (!@v) { return undef; }
200 elsif (wantarray) { return map { $_->{'value'} } @v; }
201 else { return $v[0]->{'value'}; }
204 # choice_input(text, name, &config, [display, option]+)
207 local($rv, $v, $i, @ops);
208 $rv = "<td><b>$_[0]</b></td> <td>";
209 $v = &find_value($_[1], $_[2]);
210 for($i=3; $i<@_; $i+=2) {
211 @ops = split(/,/, $_[$i+1]);
212 $rv .= "<input type=radio name=$_[1] value=\"$ops[0]\" ".
213 ($v eq $ops[0] ? "checked" : "").">$_[$i]\n";
215 return $rv."</td>\n";
218 # wide_choice_input(text, name, &config, [display, option]+)
219 sub wide_choice_input
221 local($rv, $v, $i, @ops);
222 $rv = "<td><b>$_[0]</b></td> <td colspan=3>";
223 $v = &find_value($_[1], $_[2]);
224 for($i=3; $i<@_; $i+=2) {
225 @ops = split(/,/, $_[$i+1]);
226 $rv .= "<input type=radio name=$_[1] value=\"$ops[0]\" ".
227 ($v eq $ops[0] ? "checked" : "").">$_[$i]\n";
229 return $rv."</td>\n";
232 # save_choice(name, &parent, indent)
236 if ($in{$_[0]}) { $nd = { 'name' => $_[0], 'values' => [ $in{$_[0]} ] }; }
237 &save_directive($_[1], $_[0], $nd ? [ $nd ] : [ ], $_[2], 1);
240 # addr_match_input(text, name, &config)
241 # A field for editing a list of addresses, ACLs and partial IP addresses
244 local($v, $rv, $av, @av);
245 $v = &find($_[1], $_[2]);
246 $rv = "<td><b>$_[0]</b></td> <td>";
247 $rv .= "<input type=radio name=$_[1]_def value=1 ".
248 ($v ? "" : "checked").">Default ";
249 $rv .= "<input type=radio name=$_[1]_def value=0 ".
250 ($v ? "checked" : "").">Listed..<br>";
251 foreach $av (@{$v->{'members'}}) { push(@av, $av->{'name'}); }
252 $rv .= "<textarea name=$_[1] rows=3 cols=15>".
253 join("\n", @av)."</textarea></td>\n";
258 local($addr, @vals, $dir);
259 if ($in{"$_[0]_def"}) { &save_directive($_[1], $_[0], [ ], $_[2], 1); }
261 foreach $addr (split(/\s+/, $in{$_[0]})) {
262 push(@vals, { 'name' => $addr });
264 $dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
265 &save_directive($_[1], $_[0], [ $dir ], $_[2], 1);
269 # address_input(text, name, &config, type)
272 local($v, $rv, $av, @av);
273 $v = &find($_[1], $_[2]);
274 foreach $av (@{$v->{'members'}}) { push(@av, $av->{'name'}); }
277 $rv = "<td><b>$_[0]</b></td> <td>";
278 $rv .= "<textarea name=$_[1] rows=3 cols=15>".
279 join("\n", @av)."</textarea></td>\n";
282 $rv = "<td><b>$_[0]</b></td> <td colspan=3>";
283 $rv .= "<input name=$_[1] size=50 value=\"".join(' ',@av)."\"></td>\n";
290 local($addr, @vals, $dir);
291 foreach $addr (split(/\s+/, $in{$_[0]})) {
292 &check_ipaddress($addr) || &error("'$addr' is not a valid IP address");
293 push(@vals, { 'name' => $addr });
295 $dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
296 &save_directive($_[1], $_[0], @vals ? [ $dir ] : [ ], $_[2], 1);
299 # opt_input(text, name, &config, default, size, units)
300 # Returns HTML for an optional text field
304 $v = &find($_[1], $_[2]);
305 $rv = "<td><b>$_[0]</b></td> <td nowrap";
306 $rv .= $_[4] > 30 ? " colspan=3>\n" : ">\n";
307 $rv .= sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[3]\n",
309 $rv .= sprintf "<input type=radio name=$_[1]_def value=0 %s> ",
311 $rv .= sprintf "<input name=$_[1] size=$_[4] value=\"%s\"> $_[5]</td>\n",
312 $v ? $v->{'value'} : "";
316 # save_opt(name, &func, &parent, [indent], [quote])
317 # Saves an optional text field
321 if ($in{"$_[0]_def"}) { &save_directive($_[2], $_[0], [ ], $_[3], 1); }
322 elsif ($_[1] && ($err = &{$_[1]}($in{$_[0]}))) {
326 $dir = { 'name' => $_[0],
327 'values' => [ $in{$_[0]} ],
328 'quotes' => [ $_[4] ] };
329 &save_directive($_[2], $_[0], [ $dir ], $_[3], 1);
333 # save_directive(&parent, [name|&oldvalues], &values, indent, start, [after])
334 # Given a structure containing a directive name, type, values and members
335 # add, update or remove that directive in config structure and data files.
336 # Updating of files assumes that there is no overlap between directives -
337 # each line in the config file must contain part or all of only one directive.
340 local(@oldv, @newv, $pm, $i, $o, $n, $lref, @nl);
341 $pm = $_[0]->{'members'};
342 @oldv = ref($_[1]) ? @{$_[1]} : &find($_[1], $pm);
344 for($i=0; $i<@oldv || $i<@newv; $i++) {
345 if ($i >= @oldv && $_[5]) {
346 # a new directive is being added.. put it after some other
347 $lref = $_[0]->{'file'} ? &read_file_lines($_[0]->{'file'})
349 @nl = &directive_lines($newv[$i], $_[3]);
350 $nline = $_[5]->{'line'}+1;
351 $nidx = &indexof($_[5], @$pm) + 1;
352 splice(@$lref, $nline, 0, @nl);
353 &renumber(&get_config(), $nline,
354 $_[0]->{'file'}, scalar(@nl));
355 &renumber_index($_[0]->{'members'}, $nidx, 1);
356 $newv[$i]->{'index'} = $nidx;
357 $newv[$i]->{'file'} = $_[0]->{'file'};
358 $newv[$i]->{'line'} = $nline;
359 $newv[$i]->{'eline'} = $nline + scalar(@nl);
360 splice(@$pm, $nidx, 0, $newv[$i]);
362 elsif ($i >= @oldv && $_[4]) {
363 # a new directive is being added.. put it at the start of
365 $lref = $_[0]->{'file'} ? &read_file_lines($_[0]->{'file'})
367 @nl = &directive_lines($newv[$i], $_[3]);
368 $nline = $_[0]->{'fline'}+1;
369 splice(@$lref, $nline, 0, @nl);
370 &renumber(&get_config(), $nline,
371 $_[0]->{'file'}, scalar(@nl));
372 &renumber_index($_[0]->{'members'}, 0, 1);
373 $newv[$i]->{'file'} = $_[0]->{'file'};
374 $newv[$i]->{'line'} = $nline;
375 $newv[$i]->{'eline'} = $nline + scalar(@nl);
376 unshift(@$pm, $newv[$i]);
378 elsif ($i >= @oldv) {
379 # a new directive is being added.. put it at the end of
381 $lref = $_[0]->{'file'} ? &read_file_lines($_[0]->{'file'}) : [ ];
382 @nl = &directive_lines($newv[$i], $_[3]);
383 splice(@$lref, $_[0]->{'eline'}, 0, @nl);
384 &renumber(&get_config(), $_[0]->{'eline'},
385 $_[0]->{'file'}, scalar(@nl));
386 $newv[$i]->{'file'} = $_[0]->{'file'};
387 $newv[$i]->{'line'} = $_[0]->{'eline'};
388 $newv[$i]->{'eline'} = $_[0]->{'eline'} + scalar(@nl) - 1;
389 push(@$pm, $newv[$i]);
391 elsif ($i >= @newv) {
392 # a directive was deleted
393 $lref = $oldv[$i]->{'file'} ? &read_file_lines($oldv[$i]->{'file'}) : [ ];
394 $ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
395 splice(@$lref, $oldv[$i]->{'line'}, $ol);
396 &renumber(&get_config(), $oldv[$i]->{'eline'},
397 $oldv[$i]->{'file'}, -$ol);
398 &renumber_index($_[0]->{'members'}, $oldv[$i]->{'index'}, -1);
399 splice(@$pm, &indexof($oldv[$i], @$pm), 1);
402 # updating some directive
403 if (!defined($newv[$i]->{'comment'})) {
404 $newv[$i]->{'comment'} = $oldv[$i]->{'comment'};
406 $lref = $oldv[$i]->{'file'} ? &read_file_lines($oldv[$i]->{'file'}) : [ ];
407 @nl = &directive_lines($newv[$i], $_[3]);
408 $ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
409 splice(@$lref, $oldv[$i]->{'line'}, $ol, @nl);
410 &renumber(&get_config(), $oldv[$i]->{'eline'},
411 $oldv[$i]->{'file'}, scalar(@nl) - $ol);
412 $newv[$i]->{'file'} = $_[0]->{'file'};
413 $newv[$i]->{'line'} = $oldv[$i]->{'line'};
414 $newv[$i]->{'eline'} = $oldv[$i]->{'line'} + scalar(@nl) - 1;
415 $pm->[&indexof($oldv[$i], @$pm)] = $newv[$i];
420 # directive_lines(&directive, tabs)
421 # Renders some directive into a number of lines of text
424 local(@rv, $v, $m, $i);
425 if ($_[0]->{'comment'}) {
426 push(@rv, ("\t" x $_[1])."# ".$_[0]->{'comment'});
428 local $first = "\t" x $_[1];
429 $first .= "$_[0]->{'name'}";
430 for($i=0; $i<@{$_[0]->{'values'}}; $i++) {
431 $v = $_[0]->{'values'}->[$i];
432 if ($_[0]->{'quotes'}->[$i]) { $first .= " \"$v\""; }
433 else { $first .= " $v"; }
436 if ($_[0]->{'type'}) {
437 # multiple values.. include them as well
439 foreach $m (@{$_[0]->{'members'}}) {
440 push(@rv, &directive_lines($m, $_[1]+1));
442 push(@rv, ("\t" x ($_[1]+1))."}");
444 else { $rv[$#rv] .= ";"; }
448 # renumber(&directives, line, file, count)
449 # Runs through the given array of directives and increases the line numbers
450 # of all those greater than some line by the given count
454 local ($list, $line, $file, $count) = @_;
456 foreach $d (@$list) {
457 if ($d->{'file'} eq $file) {
458 if ($d->{'line'} >= $line) { $d->{'line'} += $count; }
459 if ($d->{'eline'} >= $line) { $d->{'eline'} += $count; }
462 &renumber($d->{'members'}, $line, $file, $count);
467 # renumber_index(&directives, index, count)
471 foreach $d (@{$_[0]}) {
472 if ($d->{'index'} >= $_[1]) {
473 $d->{'index'} += $_[2];
478 # directive_diff(&d1, &d2)
479 # Do two directives differ?
483 local ($d1, $d2) = @_;
484 return 1 if ($d1->{'name'} ne $d2->{'name'});
485 local $l1 = @{$d1->{'values'}};
486 local $l2 = @{$d2->{'values'}};
487 return 1 if ($l1 != $l2);
488 for($i=0; $i<$l1; $i++) {
489 return 1 if ($d1->{'values'}->[$i] ne $d2->{'values'}->[$i]);
491 return 1 if ($d1->{'type'} != $d2->{'type'});
493 $l1 = @{$d1->{'members'}};
494 $l2 = @{$d2->{'members'}};
495 return 1 if ($l1 != $l2);
496 for($i=0; $i<$l1; $i++) {
497 return 1 if (&directive_diff($d1->{'members'}->[$i],
498 $d2->{'members'}->[$i]));
504 # group_name(&members, &group)
507 local @opts = &find("option", $_[1]->{'members'});
508 local ($dn) = grep { $_->{'values'}->[0] eq 'domain-name' } @opts;
509 return $config{'group_name'} == 1 && $dn ?
510 &text('index_gdom',$dn->{'values'}->[1]) :
511 $config{'group_name'} == 2 && $_[1]->{'comment'} ?
513 $_[1]->{'values'}->[0] ? $_[1]->{'values'}->[0] :
514 $_[0] == 0 ? $text{'index_nomemb'} :
515 $_[0] == 1 ? $text{'index_1memb'} :
516 $_[0] >= 2 && $_[0] <= 4 ? &text('index_234memb', $_[0]) :
517 &text('index_memb', $_[0]);
521 # get_subnets_and_hosts()
522 # returns the references to sorted lists of hosts and subnets
523 sub get_subnets_and_hosts
525 return (\@get_subnets_cache, \@get_hosts_cache)
526 if (@get_subnets_cache && @get_hosts_cache);
528 local(@subn,@host,@group,@shan, $s,$h,$g,$sn, $conf);
529 $conf = &get_config();
531 # get top level hosts and groups
532 @host = &find("host", $conf);
533 foreach $h (&find("host", $conf)) {
534 $h->{'order'} = $h->{'index'};
536 @group = &find("group", $conf);
537 foreach $g (@group) {
538 foreach $h (&find("host", $g->{'members'})) {
542 @subn = &find("subnet", $conf);
544 foreach $h (&find("host", $u->{'members'})) {
547 foreach $g (&find("group", $u->{'members'})) {
549 foreach $h (&find("host", $g->{'members'})) {
554 @shan = &find("shared-network", $conf);
556 foreach $h (&find("host", $s->{'members'})) {
559 foreach $g (&find("group", $s->{'members'})) {
561 foreach $h (&find("host", $g->{'members'})) {
565 foreach $u (&find("subnet", $s->{'members'})) {
567 foreach $h (&find("host", $u->{'members'})) {
570 foreach $g (&find("group", $sn->{'members'})) {
572 foreach $h (&find("host", $g->{'members'})) {
578 @get_subnets_cache = sort { $a->{'order'} <=> $b->{'order'} } @subn;
579 @get_hosts_cache = sort { $a->{'order'} <=> $b->{'order'} } @host;
581 return (\@get_subnets_cache, \@get_hosts_cache);
586 local ($sr, $hr) = &get_subnets_and_hosts();
592 local ($sr, $hr) = &get_subnets_and_hosts();
596 sub get_host_shared_network
601 if ($h->{'name'} eq 'shared-network') {
609 sub get_my_shared_network_hosts
612 local $shar = &get_host_shared_network($h);
614 foreach my $oh (&get_hosts()) {
615 if (&get_host_shared_network($oh) eq $shar) {
622 # hash that links objtypes shortcuts with object names
623 %obj_names2types = qw(host hst group grp subnet sub shared-network sha);
625 # get_branch(objtype, [addmode])
626 # usefull for edit_*.cgi and save_*.cgi scripts
627 # $objtype = one of 'hst' 'grp' 'sub' 'sha'
630 local %obj_types2names = reverse %obj_names2types;
631 local $name = $obj_types2names{$_[0]};
632 local ($parnode, $nparnode, $node, $indent, $nindent);
633 $parnode = $nparnode = &get_parent_config(
634 $_[1] && $in{'sidx'} eq '' && $in{'uidx'} eq '' && $in{'gidx'} eq '' &&
635 $in{'parent'} eq '' ? $config{'add_file'} : undef);
636 $indent = $nindent = 0;
637 foreach ($in{'sidx'}, $in{'uidx'}, $in{'gidx'}) {
639 $parnode = $parnode->{'members'}->[$_];
644 if (!($in{'delete'} && $in{'options'})) {
645 if ($in{'assign'} > 0 && !defined($in{'parent'})) {
646 # A quirk for not javascript-capable browser
647 # New parent is undefined yet; we need 2nd step
651 foreach (split(/\,/, $in{'parent'})) {
653 if ($_ < @{$nparnode->{'members'}}) {
654 $nparnode = $nparnode->{'members'}->[$_];
661 $node = $parnode->{'members'}->[$in{'idx'}];
664 die "Wrong call to get_nodes: pass objtype for new object" unless $name;
665 # Construct new node structure
666 $node->{'name'} = $name;
668 $node->{'members'} = [ ];
670 return ($parnode, $node, $indent, $nparnode, $nindent);
673 # can(permissions_string, \%access, \%config_node, smode)
674 # this is a cached wrapper of can_noncached(...)
677 local ($perm, $acc, $node, $smode) = @_;
678 if (defined($can_cache) &&
679 ($can_perm_cache eq $perm) &&
680 ($can_node_cache eq $node) &&
681 ($can_smode_cache eq $smode)) {
685 $can_perm_cache = $perm;
686 $can_node_cache = $node;
687 $can_smode_cache = $smode;
688 return ($can_cache = &can_noncached(@_));
692 # can_noncached(permissions_string, \%access, \%config_node, smode)
693 # check global and per-object permissions:
695 # permissions_string= 'c' 'r' 'w' or any combination.
696 # smode= 0 or undef - check only current, 1 - recursive childs check,
697 # 2 - check parents, 3 - check parents and all childs
698 # note: while deleting an object you must allways enforce smode=1 or 3
699 # because all child objects are deletes recursevly.
700 # this maybe an optional parameter
704 local ($perm, $acc, $node, $smode) = @_;
705 local @perm = split(//, $perm);
707 if ($node ne get_parent_config() &&
708 $node ne get_parent_config($config{'add_file'})) {
710 next if ($_ ne 'c') && ($_ ne 'r') && ($_ ne 'w');
711 return 0 unless $acc->{$_ . '_' . $obj_names2types{$node->{'name'}} };
714 # per-object permissions
715 return 0 unless &can_node(\@perm, $acc, $node);
717 if (($acc->{'smode'} == 2) || ($smode == 2) ||
718 ($acc->{'smode'} == 3) || ($smode == 3)) {
720 #$parnode=&get_parent_config();
721 #foreach ($in{'sidx'}, $in{'uidx'}, $in{'gidx'}) {
723 # $parnode = $parnode->{'members'}->[$_];
724 # return 0 unless &can_node(\@perm, $acc, $parnode);
727 $parnode = $node->{'parent'};
729 return 0 unless &can_node(\@perm, $acc, $parnode);
730 $parnode = $parnode->{'parent'};
734 if (($acc->{'smode'} == 1) || ($smode == 1) ||
735 ($acc->{'smode'} == 3) || ($smode == 3)) {
737 return 0 unless &can_subtree(\@perm, $acc, $node);
743 # can_node(\@perm, $acc, $node)
744 # checks object permissions for current node
747 local ($rperm, $acc, $node)=@_;
748 # per-object permissions
749 local $otype=$obj_names2types{$node->{'name'}};
750 if ($acc->{'per_' . $otype . '_acls'}) {
751 local $name = $node->{'values'}->[0];
752 if (!$name && $node->{'name'} eq 'group') {
753 local @opts = &find("option", $node->{'members'});
754 local ($dn) = grep { $_->{'values'}->[0] eq 'domain-name' }
757 $name = $dn->{'values'}->[1];
760 $name = $node->{'index'};
763 local $acl = $acc->{'ACL' . $otype . '_' . $name};
764 foreach (@{$rperm}) {
765 next if $_ eq 'c'; # skip creation perms for per-obj acls
766 return 0 if index($acl, $_) == -1;
772 # can_subtree(\@perm, $acc, $node)
773 # checks object permissions for subtree
776 local ($rperm, $acc, $node)=@_;
777 return 0 unless &can_node($rperm, $acc, $node);
778 if($node->{'members'}) {
779 # recursevly process this subtree
780 foreach (@{$node->{'members'}}) {
781 return 0 unless &can_subtree($rperm, $acc, $_);
787 # save_dhcpd_acl(permissions_string, obj_type, \%access, obj_name)
790 $_[2]->{'ACL'.$_[1].'_'.$_[3]} = $_[0];
792 return &save_module_acl($_[2]);
795 # drop_dhcpd_acl(obj_type, \%access, obj_name)
798 delete($_[1]->{'ACL'.$_[0].'_'.$_[2]});
800 return &save_module_acl($_[1]);
803 # find_recursive(name, &config, [parent])
804 # Returns a list of all config entries with some name, no matter where they
805 # are in the heirarchy
809 foreach $c (@{$_[1]}) {
810 if ($c->{'name'} eq $_[0]) {
814 push(@rv, &find_recursive($_[0], $c->{'members'}, $c));
820 # find_parents(&object)
823 local ($gidx, $uidx, $sidx);
824 local $p = $_[0]->{'parent'};
826 $gidx = $p->{'index'} if ($p->{'name'} eq 'group');
827 $uidx = $p->{'index'} if ($p->{'name'} eq 'subnet');
828 $sidx = $p->{'index'} if ($p->{'name'} eq 'shared-network');
831 return ($gidx, $uidx, $sidx);
834 # get_dhcpd_version(&out)
835 sub get_dhcpd_version
837 local $out = `$config{'dhcpd_path'} -v 2>&1`;
839 return $config{'version'} if ($config{'version'});
840 return undef if ($out !~ /DHCP/ || $out =~ /V1/);
841 return $out =~ /\sV([0-9\.]+)/ ? $1 :
842 $out =~ /\sDHCP\s+Server\s+([0-9\.]+)/ ? $1 :
843 $out =~ /-T/ ? 3 : 2;
847 # Re-starts the DHCP server, and returns an error message if something fails
851 if ($config{'restart_cmd'}) {
852 # Run the restart script
853 $out = &backquote_logged("$config{'restart_cmd'} 2>&1");
856 # Kill and re-run the server
857 local $pid = &is_dhcpd_running();
858 $pid && &kill_logged('TERM', $pid) ||
859 return "$text{'restart_errmsg2'} $pid : $!";
860 if ($config{'start_cmd'}) {
861 $out = &backquote_logged("$config{'start_cmd'} 2>&1");
864 $out = &backquote_logged("$config{'dhcpd_path'} -cf $config{'dhcpd_conf'} -lf $config{'lease_file'} $config{'interfaces'} 2>&1");
868 return &parse_error_out($out);
873 # Find and add config file lines around those in an error message
878 if ($out =~ /(\S+)\s+line\s+(\d+):/) {
879 local ($file, $line) = ($1, $2);
880 local $lref = &read_file_lines($file, 1);
881 local $start = $line - 5;
882 local $end = $line + 5;
883 $start = 0 if ($start < 0);
884 $end = @$lref-1 if ($end > @$lref-1);
885 $conftext = &text('restart_conftext', $line, $file)."<br>".
886 "<pre>".&html_escape(join("\n", @$lref[$start .. $end]))."</pre>";
888 return "<pre>".&html_escape($out)."</pre>".$conftext;
892 # Stop the running DHCP server. Returns undef on success, or an error message
896 if ($config{'stop_cmd'}) {
897 local $out = &backquote_logged("$config{'stop_cmd'} 2>&1");
898 return $? ? "<pre>$out</pre>" : undef;
901 local $pid = &is_dhcpd_running();
902 if ($pid && &kill_logged('TERM', $pid)) {
906 return $text{'stop_ekill'};
912 # Attempt to start the DHCP server, returning undef on success or an error
913 # message on failure.
916 if (!-r $config{'lease_file'}) {
917 # first time.. need to create the lease file
918 $config{'lease_file'} =~ /^(\S+)\/([^\/]+)$/;
919 if (!-d $1) { mkdir($1, 0755); }
920 open(LEASE, ">$config{'lease_file'}");
924 if ($config{'start_cmd'}) {
925 $out = &backquote_logged("$config{'start_cmd'} 2>&1");
928 $out = &backquote_logged("$config{'dhcpd_path'} -cf $config{'dhcpd_conf'} -lf $config{'lease_file'} $config{'interfaces'} 2>&1");
930 if ($? || $out =~ /error|failed/i) {
931 return &parse_error_out($out);
938 # search_re(value, match)
941 if ($in{'match'} == 0) {
942 return lc($_[0]) eq lc($_[1]);
944 elsif ($in{'match'} == 1) {
945 return $_[0] =~ /\Q$_[1]\E/i;
948 return eval { $_[0] =~ /$_[1]/i };
953 # Returns the DHCP server PID file
956 local $conf = &get_config();
957 local $file = &find_value("pid-file-name", $conf);
958 return $file || $config{'pid_file'};
964 local @rs = split(/\./, $s);
965 local @re = split(/\./, $e);
967 for(my $i=$rs[0]; $i<=$re[0]; $i++) {
968 for(my $j=$rs[1]; $j<=$re[1]; $j++) {
969 for(my $k=$rs[2]; $k<=$re[2]; $k++) {
970 for(my $l=$rs[3]; $l<=$re[3]; $l++) {
971 push(@rv, "$i.$j.$k.$l");
980 # Returns the pid if the DHCP server is running
983 local $pidfile = &get_pid_file();
985 return &check_pid_file($pidfile);
988 local ($pid) = &find_byname("dhcpd");