2 # $Id: index.cgi,v 1.6 2005/04/16 14:30:21 jfranken Exp $
3 # * List all subnets and shared networks
5 # File modified 2005-04-15 by Johannes Franken <jfranken@jfranken.de>:
6 # * Added support for DNS Zones (list, edit, create zone-directives)
7 # * Added a button to edit key-directives
8 # * Added a button to edit dhcp.conf in a text editor
10 require './dhcpd-lib.pl';
11 $display_max = $config{'display_max'} || 1000000000;
13 $horder = $in{'horder'};
14 $norder = $in{'norder'};
15 if ($horder eq "" && open(INDEX, "$module_config_directory/hindex.".$remote_user)) {
16 chop($horder = <INDEX>);
22 if ($norder eq "" && open(INDEX, "$module_config_directory/nindex.".$remote_user)) {
23 chop($norder = <INDEX>);
29 $nocols = $config{'dhcpd_nocols'} ? $config{'dhcpd_nocols'} : 5;
30 $conf = &get_config();
31 %access = &get_module_acl();
33 # Check if dhcpd is installed
34 if (!-x $config{'dhcpd_path'}) {
35 &ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1, 0,
36 &help_search_link("dhcpd", "man", "doc", "howto", "google"));
37 print &text('index_dhcpdnotfound', $config{'dhcpd_path'},
38 "$gconfig{'webprefix'}/config.cgi?$module_name"),"<p>\n";
40 &foreign_require("software", "software-lib.pl");
41 $lnk = &software::missing_install_link("dhcpd", $text{'index_dhcpd'},
42 "../$module_name/", $text{'index_title'});
43 print $lnk,"<p>\n" if ($lnk);
45 &ui_print_footer("/", $text{'index_return'});
49 # Check if it is the right version
50 @st = stat($config{'dhcpd_path'});
51 if ($st[7] != $config{'dhcpd_size'} || $st[9] != $config{'dhcpd_mtime'}) {
52 # File has changed .. get the version
53 local $ver = &get_dhcpd_version(\$out);
55 &ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1, 0,
56 &help_search_link("dhcpd", "man", "doc", "howto", "google"));
57 print (&text('index_dhcpdver2',$config{'dhcpd_path'},
59 print "<pre>$out</pre>\n";
60 &ui_print_footer("/", $text{'index_return'});
63 $config{'dhcpd_version'} = $ver;
64 $config{'dhcpd_size'} = $st[7];
65 $config{'dhcpd_mtime'} = $st[9];
66 &write_file("$module_config_directory/config", \%config);
69 &ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1, 0,
70 &help_search_link("dhcpd", "man", "doc", "howto", "google"),
71 undef, undef, &text('index_version', $config{'dhcpd_version'}));
73 # Create lookup type HTML
74 # XXX change text, add to lookup_*
75 $match = "<select name=match>\n";
76 $match .= "<option value=0 checked>$text{'index_match0'}\n";
77 $match .= "<option value=1>$text{'index_match1'}\n";
78 $match .= "<option value=2>$text{'index_match2'}\n";
79 $match .= "</select>\n";
82 foreach $h (&find("host", $conf)) {
85 foreach $g (&find("group", $conf)) {
87 foreach $h (&find("host", $g->{'members'})) {
89 $group{$h} = $g->{'index'};
91 push(@{$g->{'hosts'}}, $h->{'values'}->[0]);
95 # get subnets and shared nets, and the hosts and groups within them
96 @subn = &find("subnet", $conf);
98 $maxsubn = $maxsubn > $u->{'index'} ? $maxsubn : $u->{'index'};
99 foreach $h (&find("host", $u->{'members'})) {
100 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
101 $subnet{$h} = $u->{'index'};
105 foreach $g (&find("group", $u->{'members'})) {
106 $maxgroup = $maxgroup > $g->{'index'} ? $maxgroup : $g->{'index'};
107 $subnet{$g} = $u->{'index'};
110 foreach $h (&find("host", $g->{'members'})) {
111 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
112 $subnet{$h} = $u->{'index'};
113 $group{$h} = $g->{'index'};
115 push(@{$g->{'hosts'}}, $h->{'values'}->[0]);
120 @shan = &find("shared-network", $conf);
122 $maxshar = $maxshar > $s->{'index'} ? $maxshar : $s->{'index'};
123 foreach $h (&find("host", $s->{'members'})) {
124 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
125 $shared{$h} = $s->{'index'};
129 foreach $g (&find("group", $s->{'members'})) {
130 $maxgroup = $maxgroup > $g->{'index'} ? $maxgroup : $g->{'index'};
131 $shared{$g} = $s->{'index'};
134 foreach $h (&find("host", $g->{'members'})) {
135 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
136 $group{$h} = $g->{'index'};
137 $shared{$h} = $s->{'index'};
139 push(@{$g->{'hosts'}}, $h->{'values'}->[0]);
143 foreach $u (&find("subnet", $s->{'members'})) {
144 $maxsubn = $maxsubn > $u->{'index'} ? $maxsubn : $u->{'index'};
147 $shared{$u} = $s->{'index'};
148 foreach $h (&find("host", $u->{'members'})) {
149 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
150 $subnet{$h} = $u->{'index'};
151 $shared{$h} = $s->{'index'};
155 foreach $g (&find("group", $u->{'members'})) {
156 $maxgroup = $maxgroup > $g->{'index'} ? $maxgroup : $g->{'index'};
157 $subnet{$g} = $u->{'index'};
158 $shared{$g} = $s->{'index'};
161 foreach $h (&find("host", $g->{'members'})) {
162 $maxhost = $maxhost > $h->{'index'} ? $maxhost : $h->{'index'};
163 $subnet{$h} = $u->{'index'};
164 $group{$h} = $g->{'index'};
165 $shared{$h} = $s->{'index'};
167 push(@{$g->{'hosts'}}, $h->{'values'}->[0]);
174 $s->{'order'} = (1 + $s->{'index'}) * (2 + $maxsubn);
177 $s->{'order'} = (defined($shared{$s}) ? (1 + $shared{$s}) * (2 + $maxsubn) : 0)
181 @subn = (@subn, @shan);
183 elsif ($norder == 1) {
184 @subn = (@subn, @shan);
185 @subn = sort { $a->{'order'} <=> $b->{'order'} } @subn;
187 elsif ($norder == 2) {
188 @subn = sort { $a->{'values'}->[0] <=> $b->{'values'}->[0] } @subn;
189 @shan = sort { $a->{'values'}->[0] cmp $b->{'values'}->[0] } @shan;
190 @subn = (@subn, @shan);
193 # display subnets and shared nets
195 local $can_view = &can('r', \%access, $u);
196 next if !$can_view && $access{'hide'};
198 if ($u->{'name'} eq "subnet") {
199 push(@ulinks, $l = $can_view ?
200 "edit_subnet.cgi?idx=$u->{'index'}".
201 ($shared{$u} ne "" ? "&sidx=$shared{$u}" : "") :
203 push(@uicons, $i = "images/subnet.gif");
204 push(@checkboxids, $u->{'index'}.
205 ($shared{$u} ne "" ? "/$shared{$u}" : ""));
208 push(@slinks, $l = $can_view ?
209 "edit_shared.cgi?idx=$u->{'index'}" : undef);
210 push(@sicons, $i = "images/shared.gif");
211 push(@checkboxids, $u->{'index'});
213 if ($config{'desc_name'} && $u->{'comment'}) {
214 push(@utitles, $t = &html_escape($u->{'comment'}));
217 push(@utitles, $t = &html_escape($u->{'values'}->[0]));
219 push(@uslinks, $l); # so that ordering is preserved
223 @checkboxes = map { &ui_checkbox("d", $_) } @checkboxids;
224 if ($access{'r_sub'} || $access{'c_sub'} ||
225 $access{'r_sha'} || $access{'c_sha'}) {
226 print &ui_subheading($text{'index_subtitle'});
228 if (@ulinks < $display_max && @slinks < $display_max) {
233 elsif (@ulinks < $display_max) {
238 elsif (@slinks < $display_max) {
244 # Show table of subnets and shared nets
245 $show_subnet_delete = 1;
246 print &ui_form_start("delete_subnets.cgi");
248 if ($config{'hostnet_list'} == 0) {
249 &icons_table(\@links, \@titles, \@icons, $nocols,
250 undef, undef, undef, \@checkboxes);
253 &net_table(\@subn, 0, scalar(@subn), \@links, \@titles,
257 elsif (!@ulinks && !@slinks) {
258 # No subnets or shared nets
259 print "$text{'index_nosubdef'} <p>\n";
261 $show_subnet_shared = 1;
264 if ($show_subnet_delete) {
265 print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
268 # Show too-many forms
269 if ($show_subnet_shared) {
271 if (@ulinks >= $display_max) {
272 # Could not show all subnets, so show lookup form
273 print "<form action=lookup_subnet.cgi>\n";
274 print "<tr> <td><b>$text{'index_subtoomany'}</b></td>\n";
275 print "<td><input type=submit value='$text{'index_sublook2'}'></td>\n";
276 print "<td>$matches</td>\n";
277 print "<td><input name=subnet size=30></td></tr> </form>\n";
279 if (@slinks >= $display_max) {
280 # Could not show all shared nets, so show lookup form
281 print "<form action=lookup_shared.cgi>\n";
282 print "<tr> <td><b>$text{'index_shatoomany'}</b></td>\n";
283 print "<td><input type=submit value='$text{'index_shalook2'}'></td>\n";
284 print "<td>$matches</td>\n";
285 print "<td><input name=shared size=30></td></tr> </form>\n";
287 print "</tr></table>\n";
292 foreach $g (@group) {
293 $parent = (defined($subnet{$g}) ? 1 + $subnet{$g} : 0) +
294 (defined($shared{$g}) ? (1 + $shared{$g}) * (2 + $maxsubn) : 0);
295 $g->{'order'} = $parent + (1 + $g->{'index'}) / (2 + $maxgroup);
298 $parent = (defined($group{$h}) ? (1 + $group{$h}) / (2 + $maxgroup) : 0) +
299 (defined($subnet{$h}) ? 1 + $subnet{$h} : 0) +
300 (defined($shared{$h}) ? (1 + $shared{$h}) * (2 + $maxsubn) : 0);
301 $h->{'order'} = $parent + (1 + $h->{'index'}) /
302 ((1 + @group) * (2 + $maxhost));
305 @host = (@host, @group);
307 elsif ($horder == 1) {
308 @host = (@host, @group);
309 @host = sort { $a->{'line'} <=> $b->{'line'} } @host;
311 elsif ($horder == 2) {
312 @host = sort { $a->{'values'}->[0] cmp $b->{'values'}->[0] } @host;
313 @host = (@host, @group);
315 elsif ($horder == 3) {
316 @host = sort { &hardware($a) cmp &hardware($b) } @host;
317 @host = (@host, @group);
319 elsif ($horder == 4) {
320 @host = sort { &ipaddress($a) cmp &ipaddress($b) } @host;
321 @host = (@host, @group);
326 local $can_view = &can('r', \%access, $h);
327 next if !$can_view && $access{'hide'};
328 if ($h->{'name'} eq 'host') {
329 # Add icon for a host
330 push(@hlinks, $l = $can_view ?
331 "edit_host.cgi?idx=$h->{'index'}".
332 (defined($group{$h}) ? "&gidx=$group{$h}" : "").
333 (defined($subnet{$h}) ? "&uidx=$subnet{$h}" : "").
334 (defined($shared{$h}) ? "&sidx=$shared{$h}" : "") :
336 if ($config{'desc_name'} && $h->{'comment'}) {
337 push(@htitles, &html_escape($h->{'comment'}));
340 push(@htitles, &html_escape($h->{'values'}->[0]));
342 if ($config{'show_ip'}) {
343 $fv = &fixedaddr($h);
344 $htitles[$#htitles] .= "<br>".$fv if ($fv);
346 if ($config{'show_mac'}) {
347 local $hard = &find("hardware", $h->{'members'});
348 $htitles[$#htitles] .= "<br>$hard->{'values'}->[1]"
351 $t = $htitles[$#htitles];
352 push(@hicons, $i = "images/host.gif");
353 push(@hgcheckboxids, join("/", $h->{'index'}, $group{$h},
354 $subnet{$h}, $shared{$h}));
357 # Add icon for a group
358 push(@glinks, $l = $can_view ?
359 "edit_group.cgi?idx=$h->{'index'}".
360 (defined($subnet{$h}) ? "&uidx=$subnet{$h}" : "").
361 (defined($shared{$h}) ? "&sidx=$shared{$h}" : "") :
363 $gm = @{$h->{'hosts'}};
364 push(@gtitles, $t = &html_escape(&group_name($gm, $h)));
365 push(@gicons, $i = "images/group.gif");
366 push(@hgcheckboxids, join("/", $h->{'index'}, $subnet{$h},
373 @hgcheckboxes = map { &ui_checkbox("d", $_) } @hgcheckboxids;
374 if ($access{'r_hst'} || $access{'c_hst'} ||
375 $access{'r_grp'} || $access{'c_grp'}) {
376 print &ui_subheading($text{'index_hst'});
378 if (@hlinks < $display_max && @glinks < $display_max) {
383 elsif (@hlinks < $display_max) {
388 elsif (@glinks < $display_max) {
394 # Some hosts or groups to show
395 &index_links($horder, "h", 5, $text{'index_hdisplay'},
397 $show_host_delete = 1;
398 print &ui_form_start("delete_hosts.cgi");
400 if ($config{'hostnet_list'} == 0) {
401 &icons_table(\@links, \@titles, \@icons, $nocols,
402 undef, undef, undef, \@hgcheckboxes);
405 &host_table(\@host, 0, scalar(@host), \@links, \@titles,
409 elsif (!@hlinks && !@glinks) {
410 # None to show at all
411 print "$text{'index_nohst'} <p>\n";
413 $show_host_group = 1;
416 if ($show_host_delete) {
417 print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
420 # Show too-many forms
421 if ($show_host_group) {
423 if (@hlinks >= $display_max) {
424 # Could not show all hosts, so show lookup form
425 print "<form action=lookup_host.cgi>\n";
426 print "<tr> <td><b>$text{'index_hsttoomany'}</b></td>\n";
427 print "<td><input type=submit value='$text{'index_hstlook2'}'></td>\n";
428 print "<td>$matches</td>\n";
429 print "<td><input name=host size=30></td></tr> </form>\n";
431 if (@glinks >= $display_max) {
432 # Could not show all groups, so show lookup form
433 print "<form action=lookup_group.cgi>\n";
434 print "<tr> <td><b>$text{'index_grptoomany'}</b></td>\n";
435 print "<td><input type=submit value='$text{'index_grplook2'}'></td>\n";
436 print "<td>$matches</td>\n";
437 print "<td><input name=group size=30></td></tr> </form>\n";
439 print "</tr></table>\n";
444 ############ START ZONES #####
446 if ($config{'dhcpd_version'} >= 3) {
447 print &ui_subheading($text{'zone_key'});
450 @zones = &find("zone", $conf);
451 @zones = sort { $a->{'values'}->[0] <=> $b->{'values'}->[0] } @zones;
454 print "<a href='edit_zones.cgi?new=1'>","$text{'index_addzone'}</a> \n" if $access{'c_sub'};
455 foreach $z (@zones) {
456 # print "ZONE: $z->{'value'} <br>";
457 push(@zlinks, "edit_zones.cgi?idx=$z->{'index'}");
458 push(@zicons, "images/files.gif");
459 push(@ztitles, &html_escape($z->{'values'}->[0]));
461 if ($config{'hostnet_list'} == 0) {
463 &icons_table(\@zlinks, \@ztitles, \@zicons, $nocols);
467 &zone_table(\@zones, 0, scalar(@zones), \@zlinks, \@ztitles);
471 print "<b>$text{'index_nozones'}</b><p>\n";
473 print "<a href='edit_zones.cgi?new=1'>","$text{'index_addzone'}</a> \n" if $access{'c_sub'};
478 # Buttons for global actions
479 print &ui_buttons_start();
481 if ($access{'global'}) {
482 # Edit global client options
483 print &ui_buttons_row("edit_options.cgi",
484 $text{'index_buttego'}, $text{'index_ego'},
485 &ui_hidden("global", 1));
488 if ($config{'dhcpd_version'} >= 3 && $access{'global'}) {
490 print &ui_buttons_row("edit_keys.cgi",
491 $text{'index_buttekey'}, $text{'index_key'});
493 if (!$access{'noconfig'}) {
494 # Manually edit config file
495 print &ui_buttons_row("edit_text.cgi",
496 $text{'index_buttetext'}, $text{'index_text'});
498 if (!$access{'noconfig'}) {
499 # Select network interfaces
500 print &ui_buttons_row("edit_iface.cgi",
501 $text{'index_buttiface'}, $text{'index_iface'});
503 if ($access{'r_leases'}) {
505 print &ui_buttons_row("list_leases.cgi",
506 $text{'index_buttlal'}, $text{'index_lal'});
508 if ($access{'apply'}) {
509 $pid = &is_dhcpd_running();
511 # Apply and stop buttons
512 print &ui_buttons_row("restart.cgi",
513 $text{'index_buttapply'}, $text{'index_apply'},
514 &ui_hidden("pid", $pid));
515 print &ui_buttons_row("stop.cgi",
516 $text{'index_stop'}, $text{'index_stopdesc'},
517 &ui_hidden("pid", $pid));
521 print &ui_buttons_row("start.cgi",
522 $text{'index_buttstart'}, $text{'index_start'});
525 print &ui_buttons_end();
527 &ui_print_footer("/", $text{'index_return'});
529 # Returns canonized hardware address.
531 local ($hconf, $addr);
532 $hconf = $_[0]->{'members'} ? &find("hardware", $_[0]->{'members'}) : undef;
534 $addr = uc($hconf->{'values'}->[1]);
535 $addr =~ s/(^|\:)([0-9A-F])(?=$|\:)/$1\x30$2/g;
537 return $hconf ? $addr : undef;
540 # Returns ip address for sorting on
543 return undef if (!$_[0]->{'members'});
544 local $iconf = &find("fixed-address", $_[0]->{'members'});
545 return undef if (!$iconf);
546 return sprintf "%3.3d.%3.3d.%3.3d.%3.3d",
547 split(/\./, $iconf->{'values'}->[0]);
551 local ($fixed, $addr);
552 $fixed = &find("fixed-address", $_[0]->{'members'});
554 $addr = join(" ", grep { $_ ne "," } @{$fixed->{'values'}});
562 return $_[0]->{'values'}->[2];
565 # index_links(current, name, max, txt, ref)
568 local (%linkname, $l);
570 for (my $l = 0; $l < $_[2]; $l++) {
571 my $msg = $text{"index_$_[1]order$l"};
576 push(@links, "<a href='?$_[1]order=$l\&$_[4]'>$msg</a>");
579 print "<b>$_[3]</b> ",&ui_links_row(\@links),"\n";
580 open(INDEX, "> $module_config_directory/$_[1]index.".$remote_user);
581 print INDEX "$_[0]\n";
587 local ($i, $h, $parent);
588 local @tds = ( "width=5" );
589 print &ui_columns_start([ "", $text{'index_hostgroup'},
590 $text{'index_parent'}, $text{'index_hardware'},
591 $text{'index_nameip'} ], 100, 0, \@tds);
592 for ($i = $_[1]; $i < $_[2]; $i++) {
596 if ($h->{'name'} eq 'host') {
600 $firstcol .= $text{'index_group'}." ";
601 $sp = "\ \ ";
604 $firstcol .= "<a href=$_[3]->[$i]>$_[4]->[$i]</a>";
607 $firstcol .= $_[4]->[$i];
609 push(@cols, $firstcol);
611 if ($par{$h}->{'name'} eq "group") {
612 $par_type = $text{'index_togroup'};
613 $parent = &group_name(scalar @{$par{$h}->{'hosts'}});
614 } elsif ($par{$h}->{'name'} eq "subnet") {
615 $par_type = $text{'index_tosubnet'};
616 $parent = $par{$h}->{'values'}->[0];
617 } elsif ($par{$h}->{'name'} eq "shared-network") {
618 $par_type = $text{'index_toshared'};
619 $parent = $par{$h}->{'values'}->[0];
622 if ($config{'desc_name'} && $par{$h}->{'comment'}) {
623 $parent = $par{$h}->{'comment'};
625 push(@cols, "$par_type: $parent");
626 push(@cols, $_[3]->[$i] ? &hardware($h) : "");
627 push(@cols, $_[3]->[$i] ? &fixedaddr($h) : "");
628 print &ui_checked_columns_row(\@cols, \@tds, "d", $_[5]->[$i]);
630 print &ui_columns_end();
633 #&net_table(\@subn, 0, scalar(@subn), \@links, \@titles, \@checkboxids);
637 local @tds = ( "width=5" );
638 print &ui_columns_start([ "", $text{'index_net'}, $text{'index_netmask'},
639 $text{'index_desc'}, $text{'index_parent'} ], 100,
641 for ($i = $_[1]; $i < $_[2]; $i++) {
645 if ($n->{'name'} eq 'subnet') {
649 $sp = "\ \ ";
652 $first .= "<a href=$_[3]->[$i]>$_[4]->[$i]</a>";
655 $first .= $_[4]->[$i];
658 push(@cols, $_[3]->[$i] ? &netmask($n) : "");
659 push(@cols, $n->{'comment'});
660 push(@cols, $par{$n} ?
661 "$text{'index_toshared'} $par{$n}->{'values'}->[0]" : "");
662 print &ui_checked_columns_row(\@cols, \@tds, "d", $_[5]->[$i]);
664 print &ui_columns_end();
668 #&zone_table(\@zones, 0, scalar(@zones), \@zlinks, \@ztitles);
672 print "<table border width=95%>\n";
673 print "<tr $tb> <td><b>", $text{'index_zone'}, "</b></td></tr>\n";
674 for ($i = $_[1]; $i < $_[2]; $i++) {
675 print "<tr $cb> <td>\n";
676 print "<a href=$_[3]->[$i]>", $_[4]->[$i], "</a> </td>\n";
685 if ($show_subnet_delete) {
686 push(@links, &select_all_link("d"),
687 &select_invert_link("d"));
689 push(@links, "<a href='edit_subnet.cgi?new=1'>$text{'index_addsub'}</a>")
691 push(@links, "<a href='edit_shared.cgi?new=1'>$text{'index_addnet'}</a>")
693 print &ui_links_row(\@links);
699 if ($show_host_delete) {
700 push(@links, &select_all_link("d", 1),
701 &select_invert_link("d", 1));
703 push(@links, "<a href='edit_host.cgi?new=1'>$text{'index_addhst'}</a>")
705 push(@links, "<a href='edit_group.cgi?new=1'>$text{'index_addhstg'}</a>")
707 print &ui_links_row(\@links);