2 # Functions for parsing the DHCPD config file
8 # Returns a dummy parent structure for the DHCP config
11 return $get_parent_config_cache if ($get_parent_config_cache);
12 return ($get_parent_config_cache =
14 'file' => $config{'dhcpd_conf'},
15 'members' => &get_config(),
17 'eline' => $get_config_lines
22 # Parses the DHCPD config file into a data structure
25 return \@get_config_cache if (@get_config_cache);
26 local(@tok, @rv, $i, $t, $j, $ifile, @inc, $str);
28 $get_config_lines = &tokenize_file($config{'dhcpd_conf'}, \@tok);
31 $str = &parse_struct(\@tok, \$i, $j++, $config{'dhcpd_conf'});
32 if ($str) { push(@rv, $str); }
34 @get_config_cache = @rv;
35 return \@get_config_cache;
38 # tokenize_file(file, &tokens)
42 local ($line, $cmode);
44 while($line = <FILE>) {
47 $line =~ s/^([^"#]*)#.*$/$1/g;
48 $line =~ s/^([^"]*)\/\/.*$/$1/g;
50 if (!$cmode && $line =~ /\/\*/) {
51 # start of a C-style comment
53 $line =~ s/\/\*.*$//g;
56 if ($line =~ /\*\//) {
59 $line =~ s/^.*\*\///g;
61 else { $line = ""; last; }
66 # split line into tokens
68 if ($line =~ /^\s*"([^"]*)"(.*)$/) {
69 push(@{$_[1]}, [ $1, 1, $lines ]); $line = $2;
71 elsif ($line =~ /^\s*([{};])(.*)$/) {
72 push(@{$_[1]}, [ $1, 0, $lines ]); $line = $2;
74 elsif ($line =~ /^\s*([^{}; \t]+)(.*)$/) {
75 push(@{$_[1]}, [ $1, 0, $lines ]); $line = $2;
85 # parse_struct(&tokens, &token_num, index, file)
86 # A structure can either have one value, or a list of values.
87 # Pos will end up at the start of the next structure
90 local(%str, $i, $t, @vals, @quotes, $str, @text);
92 $str{'name'} = $_[0]->[$i]->[0];
93 $str{'line'} = $_[0]->[$i]->[2];
96 if ($t->[0] eq "{" || $t->[0] eq ";") { last; }
97 elsif (!defined($t->[0])) { ${$_[1]} = $i; return undef; }
98 else { push(@vals, $t->[0]); push(@quotes, $t->[1]); }
99 push(@text, $t->[1] ? "\"$t->[0]\"" : $t->[0]);
101 $str{'values'} = \@vals;
102 $str{'quotes'} = \@quotes;
103 $str{'value'} = $vals[0];
104 $str{'text'} = join(" ", @text);
105 $str{'index'} = $_[2];
106 $str{'file'} = $_[3];
107 if ($t->[0] eq "{") {
108 # contains sub-structures.. parse them
113 while($_[0]->[$i]->[0] ne "}") {
114 if (!defined($_[0]->[$i]->[0]))
115 { ${$_[1]} = $i; return undef; }
116 $str = &parse_struct($_[0], \$i, $j++, $_[3]);
117 if ($str) { push(@mems, $str); }
119 $str{'members'} = \@mems;
120 $i++; # skip trailing }
123 # only a single value..
125 $i++; # skip trailing ;
127 $str{'eline'} = $_[0]->[$i-1]->[2]; # ending line is the line number the
137 foreach $c (@{$_[1]}) {
138 if ($c->{'name'} eq $_[0]) {
142 return @rv ? wantarray ? @rv : $rv[0]
143 : wantarray ? () : undef;
146 # find_value(name, &array)
150 @v = &find($_[0], $_[1]);
151 if (!@v) { return undef; }
152 elsif (wantarray) { return map { $_->{'value'} } @v; }
153 else { return $v[0]->{'value'}; }
156 # choice_input(text, name, &config, [display, option]+)
159 local($rv, $v, $i, @ops);
160 $rv = "<td><b>$_[0]</b></td> <td>";
161 $v = &find_value($_[1], $_[2]);
162 for($i=3; $i<@_; $i+=2) {
163 @ops = split(/,/, $_[$i+1]);
164 $rv .= "<input type=radio name=$_[1] value=\"$ops[0]\" ".
165 ($v eq $ops[0] ? "checked" : "").">$_[$i]\n";
167 return $rv."</td>\n";
170 # save_choice(name, &parent, indent)
174 if ($in{$_[0]}) { $nd = { 'name' => $_[0], 'values' => [ $in{$_[0]} ] }; }
175 &save_directive($_[1], $_[0], $nd ? [ $nd ] : [ ], $_[2], 1);
178 # addr_match_input(text, name, &config)
179 # A field for editing a list of addresses, ACLs and partial IP addresses
182 local($v, $rv, $av, @av);
183 $v = &find($_[1], $_[2]);
184 $rv = "<td><b>$_[0]</b></td> <td>";
185 $rv .= "<input type=radio name=$_[1]_def value=1 ".
186 ($v ? "" : "checked").">Default ";
187 $rv .= "<input type=radio name=$_[1]_def value=0 ".
188 ($v ? "checked" : "").">Listed..<br>";
189 foreach $av (@{$v->{'members'}}) { push(@av, $av->{'name'}); }
190 $rv .= "<textarea name=$_[1] rows=3 cols=15>".
191 join("\n", @av)."</textarea></td>\n";
196 local($addr, @vals, $dir);
197 if ($in{"$_[0]_def"}) { &save_directive($_[1], $_[0], [ ], $_[2], 1); }
199 foreach $addr (split(/\s+/, $in{$_[0]})) {
200 push(@vals, { 'name' => $addr });
202 $dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
203 &save_directive($_[1], $_[0], [ $dir ], $_[2], 1);
207 # address_input(text, name, &config, type)
210 local($v, $rv, $av, @av);
211 $v = &find($_[1], $_[2]);
212 foreach $av (@{$v->{'members'}}) { push(@av, $av->{'name'}); }
215 $rv = "<td><b>$_[0]</b></td> <td>";
216 $rv .= "<textarea name=$_[1] rows=3 cols=15>".
217 join("\n", @av)."</textarea></td>\n";
220 $rv = "<td><b>$_[0]</b></td> <td colspan=3>";
221 $rv .= "<input name=$_[1] size=50 value=\"".join(' ',@av)."\"></td>\n";
228 local($addr, @vals, $dir);
229 foreach $addr (split(/\s+/, $in{$_[0]})) {
230 &check_ipaddress($addr) || &error("'$addr' is not a valid IP address");
231 push(@vals, { 'name' => $addr });
233 $dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
234 &save_directive($_[1], $_[0], @vals ? [ $dir ] : [ ], $_[2], 1);
237 # opt_input(text, name, &config, default, size, units)
241 $v = &find($_[1], $_[2]);
242 $rv = "<td><b>$_[0]</b></td> <td nowrap";
243 $rv .= $_[4] > 30 ? " colspan=3>\n" : ">\n";
244 $rv .= sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[3]\n",
246 $rv .= sprintf "<input type=radio name=$_[1]_def value=0 %s> ",
248 $rv .= sprintf "<input name=$_[1] size=$_[4] value=\"%s\"> $_[5]</td>\n",
249 $v ? $v->{'value'} : "";
253 # save_opt(name, &func, &parent, [indent], [quote])
257 if ($in{"$_[0]_def"}) { &save_directive($_[2], $_[0], [ ], $_[3], 1); }
258 elsif ($_[1] && ($err = &{$_[1]}($in{$_[0]}))) {
262 $dir = { 'name' => $_[0],
263 'values' => [ $in{$_[0]} ],
264 'quotes' => [ $_[4] ] };
265 &save_directive($_[2], $_[0], [ $dir ], $_[3], 1);
269 # save_directive(&parent, [name|&oldvalues], &values, indent, start)
270 # Given a structure containing a directive name, type, values and members
271 # add, update or remove that directive in config structure and data files.
272 # Updating of files assumes that there is no overlap between directives -
273 # each line in the config file must contain part or all of only one directive.
276 local(@oldv, @newv, $pm, $i, $o, $n, $lref, @nl);
277 $pm = $_[0]->{'members'};
278 @oldv = ref($_[1]) ? @{$_[1]} : &find($_[1], $pm);
280 for($i=0; $i<@oldv || $i<@newv; $i++) {
281 if ($i >= @oldv && $_[4]) {
282 # a new directive is being added.. put it at the start of
284 $lref = &read_file_lines($_[0]->{'file'});
285 @nl = &directive_lines($newv[$i], $_[3]);
286 splice(@$lref, $_[0]->{'line'}+1, 0, @nl);
287 &renumber(&get_config(), $_[0]->{'line'}+1,
288 $_[0]->{'file'}, scalar(@nl));
289 &renumber_index($_[0]->{'members'}, 0, 1);
290 $newv[$i]->{'file'} = $_[0]->{'file'};
291 $newv[$i]->{'line'} = $_[0]->{'line'}+1;
292 $newv[$i]->{'eline'} = $_[0]->{'line'} + scalar(@nl);
293 unshift(@$pm, $newv[$i]);
295 elsif ($i >= @oldv) {
296 # a new directive is being added.. put it at the end of
298 $lref = &read_file_lines($_[0]->{'file'});
299 @nl = &directive_lines($newv[$i], $_[3]);
300 splice(@$lref, $_[0]->{'eline'}, 0, @nl);
301 &renumber(&get_config(), $_[0]->{'eline'},
302 $_[0]->{'file'}, scalar(@nl));
303 $newv[$i]->{'file'} = $_[0]->{'file'};
304 $newv[$i]->{'line'} = $_[0]->{'eline'};
305 $newv[$i]->{'eline'} = $_[0]->{'eline'} + scalar(@nl) - 1;
306 push(@$pm, $newv[$i]);
308 elsif ($i >= @newv) {
309 # a directive was deleted
310 $lref = &read_file_lines($oldv[$i]->{'file'});
311 $ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
312 splice(@$lref, $oldv[$i]->{'line'}, $ol);
313 &renumber(&get_config(), $oldv[$i]->{'eline'},
314 $oldv[$i]->{'file'}, -$ol);
315 &renumber_index($_[0]->{'members'}, $oldv[$i]->{'index'}, -1);
316 splice(@$pm, &indexof($oldv[$i], @$pm), 1);
319 # updating some directive
320 $lref = &read_file_lines($oldv[$i]->{'file'});
321 @nl = &directive_lines($newv[$i], $_[3]);
322 $ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
323 &renumber(&get_config(), $oldv[$i]->{'eline'},
324 $oldv[$i]->{'file'}, scalar(@nl) - $ol);
325 splice(@$lref, $oldv[$i]->{'line'}, $ol, @nl);
326 $newv[$i]->{'file'} = $_[0]->{'file'};
327 $newv[$i]->{'line'} = $oldv[$i]->{'line'};
328 $newv[$i]->{'eline'} = $oldv[$i]->{'line'} + scalar(@nl) - 1;
329 $pm->[&indexof($oldv[$i], @$pm)] = $newv[$i];
334 # directive_lines(&directive, tabs)
335 # Renders some directive into a number of lines of text
338 local(@rv, $v, $m, $i);
339 $rv[0] = "\t" x $_[1];
340 $rv[0] .= "$_[0]->{'name'}";
341 for($i=0; $i<@{$_[0]->{'values'}}; $i++) {
342 $v = $_[0]->{'values'}->[$i];
343 if ($_[0]->{'quotes'}->[$i]) { $rv[0] .= " \"$v\""; }
344 else { $rv[0] .= " $v"; }
346 if ($_[0]->{'type'}) {
347 # multiple values.. include them as well
349 foreach $m (@{$_[0]->{'members'}}) {
350 push(@rv, &directive_lines($m, $_[1]+1));
352 push(@rv, ("\t" x ($_[1]+1))."}");
354 else { $rv[$#rv] .= ";"; }
358 # renumber(&directives, line, file, count)
359 # Runs through the given array of directives and increases the line numbers
360 # of all those greater than some line by the given count
364 local ($list, $line, $file, $count) = @_;
366 foreach $d (@$list) {
367 if ($d->{'file'} eq $file) {
368 if ($d->{'line'} >= $line) { $d->{'line'} += $count; }
369 if ($d->{'eline'} >= $line) { $d->{'eline'} += $count; }
372 &renumber($d->{'members'}, $line, $file, $count);
377 # renumber_index(&directives, index, count)
381 foreach $d (@{$_[0]}) {
382 if ($d->{'index'} >= $_[1]) {
383 $d->{'index'} += $_[2];
388 # directive_diff(&d1, &d2)
389 # Do two directives differ?
393 local ($d1, $d2) = @_;
394 return 1 if ($d1->{'name'} ne $d2->{'name'});
395 local $l1 = @{$d1->{'values'}};
396 local $l2 = @{$d2->{'values'}};
397 return 1 if ($l1 != $l2);
398 for($i=0; $i<$l1; $i++) {
399 return 1 if ($d1->{'values'}->[$i] ne $d2->{'values'}->[$i]);
401 return 1 if ($d1->{'type'} != $d2->{'type'});
403 $l1 = @{$d1->{'members'}};
404 $l2 = @{$d2->{'members'}};
405 return 1 if ($l1 != $l2);
406 for($i=0; $i<$l1; $i++) {
407 return 1 if (&directive_diff($d1->{'members'}->[$i],
408 $d2->{'members'}->[$i]));
414 # group_name($members)
417 return ($_[0] == 0) ? $text{'index_nomemb'} :
418 ($_[0] == 1) ? $text{'index_1memb'} :
419 ($_[0] >= 2 && $_[0] <= 4) ? &text('index_234memb', $_[0]) :
420 &text('index_memb', $_[0]);
424 # get_subnets_and_hosts()
425 # returns the references to sorted lists of hosts and subnets
426 sub get_subnets_and_hosts
428 return (\@get_subnets_cache, \@get_hosts_cache)
429 if (@get_subnets_cache && @get_hosts_cache);
431 local(@subn,@host,@group,@shan, $s,$h,$g,$sn, $conf);
432 $conf = &get_config();
434 # get top level hosts and groups
435 @host = &find("host", $conf);
436 foreach $h (&find("host", $conf)) {
437 $h->{'order'} = $h->{'index'};
439 @group = &find("group", $conf);
440 foreach $g (@group) {
441 foreach $h (&find("host", $g->{'members'})) {
445 @subn = &find("subnet", $conf);
447 foreach $h (&find("host", $u->{'members'})) {
450 foreach $g (&find("group", $u->{'members'})) {
452 foreach $h (&find("host", $g->{'members'})) {
457 @shan = &find("shared-network", $conf);
459 foreach $h (&find("host", $s->{'members'})) {
462 foreach $g (&find("group", $s->{'members'})) {
464 foreach $h (&find("host", $g->{'members'})) {
468 foreach $u (&find("subnet", $s->{'members'})) {
470 foreach $h (&find("host", $u->{'members'})) {
473 foreach $g (&find("group", $sn->{'members'})) {
475 foreach $h (&find("host", $g->{'members'})) {
481 @get_subnets_cache = sort { $a->{'order'} <=> $b->{'order'} } @subn;
482 @get_hosts_cache = sort { $a->{'order'} <=> $b->{'order'} } @host;
484 return (\@get_subnets_cache, \@get_hosts_cache);
489 local ($sr, $hr) = &get_subnets_and_hosts();
495 local ($sr, $hr) = &get_subnets_and_hosts();
499 # hash that links objtypes shortcuts with object names
500 %obj_names2types = qw(host hst group grp subnet sub shared-network sha);
502 # get_branch($objtype)
503 # usefull for edit_*.cgi and save_*.cgi scripts
504 # $objtype = one of 'hst' 'grp' 'sub' 'sha'
507 local %obj_types2names = reverse %obj_names2types;
508 local $name = $obj_types2names{$_[0]};
509 local ($parnode, $nparnode, $node, $indent, $nindent);
510 $parnode = $nparnode = &get_parent_config();
511 $indent = $nindent = 0;
512 foreach ($in{'sidx'}, $in{'uidx'}, $in{'gidx'}) {
514 $parnode = $parnode->{'members'}->[$_];
519 if (!($in{'delete'} && $in{'options'})) {
520 if ($in{'assign'} > 0 && !defined($in{'parent'})) {
521 # A quirk for not javascript-capable browser
522 # New parent is undefined yet; we need 2nd step
526 foreach (split(/\,/, $in{'parent'})) {
528 if ($_ < @{$nparnode->{'members'}}) {
529 $nparnode = $nparnode->{'members'}->[$_];
536 $node = $parnode->{'members'}->[$in{'idx'}];
539 die "Wrong call to get_nodes: pass objtype for new object" unless $name;
540 # Construct new node structure
541 $node->{'name'} = $name;
543 $node->{'members'} = [ ];
545 return ($parnode, $node, $indent, $nparnode, $nindent);
548 # can(permissions_string, \%access, \%config_node, smode)
549 # this is a cached wrapper of can_noncached(...)
552 local ($perm, $acc, $node, $smode) = @_;
553 if (defined($can_cache) &&
554 ($can_perm_cache eq $perm) &&
555 ($can_node_cache eq $node) &&
556 ($can_smode_cache eq $smode)) {
560 $can_perm_cache = $perm;
561 $can_node_cache = $node;
562 $can_smode_cache = $smode;
563 return ($can_cache = &can_noncached(@_));
567 # can_noncached(permissions_string, \%access, \%config_node, smode)
568 # check global and per-object permissions:
570 # permissions_string= 'c' 'r' 'w' or any combination.
571 # smode= 0 or undef - check only current, 1 - recursive childs check,
572 # 2 - check parents, 3 - check parents and all childs
573 # note: while deleting an object you must allways enforce smode=1 or 3
574 # because all child objects are deletes recursevly.
575 # this maybe an optional parameter
579 local ($perm, $acc, $node, $smode) = @_;
580 local @perm = split(//, $perm);
582 if ($node ne get_parent_config()) {
584 next if ($_ ne 'c') && ($_ ne 'r') && ($_ ne 'w');
585 return 0 unless $acc->{$_ . '_' . $obj_names2types{$node->{'name'}} };
588 # per-object permissions
589 return 0 unless &can_node(\@perm, $acc, $node);
591 if (($acc->{'smode'} == 2) || ($smode == 2) ||
592 ($acc->{'smode'} == 3) || ($smode == 3)) {
594 $parnode=&get_parent_config();
595 foreach ($in{'sidx'}, $in{'uidx'}, $in{'gidx'}) {
597 $parnode = $parnode->{'members'}->[$_];
598 return 0 unless &can_node(\@perm, $acc, $parnode);
603 if (($acc->{'smode'} == 1) || ($smode == 1) ||
604 ($acc->{'smode'} == 3) || ($smode == 3)) {
606 return 0 unless &can_subtree(\@perm, $acc, $node);
612 # can_node(\@perm, $acc, $node)
613 # checks object permissions for current node
616 local ($rperm, $acc, $node)=@_;
617 # per-object permissions
618 local $otype=$obj_names2types{$node->{'name'}};
619 if ($acc->{'per_' . $otype . '_acls'}) {
620 local $acl = $acc->{'ACL' . $otype . '_' . $node->{'values'}->[0]};
621 foreach (@{$rperm}) {
622 next if $_ eq 'c'; # skip creation perms for per-obj acls
623 return 0 if index($acl, $_) == -1;
629 # can_subtree(\@perm, $acc, $node)
630 # checks object permissions for subtree
633 local ($rperm, $acc, $node)=@_;
634 return 0 unless &can_node($rperm, $acc, $node);
635 if($node->{'members'}) {
636 # recursevly process this subtree
637 foreach (@{$node->{'members'}}) {
638 return 0 unless &can_subtree($rperm, $acc, $_);
644 # save_dhcpd_acl(permissions_string, obj_type, \%access, obj_name)
647 $_[2]->{'ACL'.$_[1].'_'.$_[3]} = $_[0];
649 return &save_module_acl($_[2]);
652 # drop_dhcpd_acl(obj_type, \%access, obj_name)
655 delete($_[1]->{'ACL'.$_[0].'_'.$_[2]});
657 return &save_module_acl($_[1]);