#!/usr/bin/env perl #-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- # # Functions for dhcp server configuration # # Copyright (C) 2001 Ximian, Inc. # # Authors: Chema Celorio # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published # by the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. $SCRIPTSDIR = "@scriptsdir@"; if ($SCRIPTSDIR =~ /^@scriptsdir[@]/) { $SCRIPTSDIR = "."; $DOTIN = ".in"; } require "$SCRIPTSDIR/general.pl$DOTIN"; require "$SCRIPTSDIR/file.pl$DOTIN"; require "$SCRIPTSDIR/parse.pl$DOTIN"; require "$SCRIPTSDIR/replace.pl$DOTIN"; require "$SCRIPTSDIR/service.pl$DOTIN"; require "$SCRIPTSDIR/util.pl$DOTIN"; require "$SCRIPTSDIR/xml.pl$DOTIN"; require "$SCRIPTSDIR/tokenizer.pl$DOTIN"; ## ------------------------ Get --------------------------------------------- sub gst_dhcpd_get_host { my ($in, $out) = @_; my ($mac_address, $next_server, $ip); my %host; my $comment = &gst_dhcpd_get_host_comment ($out); my $host_name = &gst_tokenize_get_token ($in, $out); &gst_tokenize_verify_token ($in, "{", $out); while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { if ("\}" eq $token) { last; } elsif ("hardware" eq $token) { &gst_tokenize_verify_token ($in, "ethernet", $out); $mac_address = &gst_tokenize_get_token ($in, $out); &gst_tokenize_verify_token ($in, ";", $out); } elsif ("fixed-address" eq $token) { $ip = &gst_tokenize_get_token ($in, $out); &gst_tokenize_verify_token ($in, ";", $out); } elsif ("next-server" eq $token) { &gst_tokenize_skip_till ($in, ";", $out); } else { &gst_tokenize_warning ("Unexpected token \"$token\""); &gst_tokenize_skip_till ($in, ";", $out); } } $host{"host_name"} = $host_name if (defined $host_name); $host{"mac_address"} = $mac_address if (defined $mac_address); $host{"next_server"} = $next_server if (defined $next_server); $host{"ip"} = $ip if (defined $ip); $host{"comment"} = $comment if (defined $comment); return %host; } sub gst_dhcpd_get_range { my ($in, $out) = @_; my $size = 0; my @tokens; my %hash; my $token; # range is tricky because it can contain from 1 to 3 tokens # # range a; start = a, end = a, bootp = FALSE # range a b; start = a, end = b, bootp = FALSE # range dynamic-bootp a b; start = a, end = b, bootp = TRUE # range dynamic-bootp a; start = a, end = a, bootp = TRUE $token = &gst_tokenize_get_token_till ($in, $out, ";"); # bootp for this range ? $_ = $token; if (/^dynamic-bootp /) { $token = $'; # remove dynamic-bootp from token $hash{"bootp"} = 1; } else { $hash{"bootp3"} = 0; } # If it does not contain a whitespace duplicate the value # since if max is ommited, max = min $_ = $token; if (not m/ /) { $token .= " " . $token } # Get the start and end token @tokens = split (' ', $token); $hash{"start"} = $tokens[0]; $hash{"end"} = $tokens[1]; my $empty = $tokens[2]; # We should not have any tokens left if (defined ($empty)) { &gst_tokenize_warning ("Unexpected token $empty"); } return %hash; } sub gst_dhcpd_get_subnet { my ($in, $out) = @_; my %options; my (%ranges, %j, %r_1); my (%hosts, %host_1); my @known_options = qw(domain-name routers); my @ignore_options = qw(nis-domain time-offset ntp-servers subnet-mask); $options{"ip"} = &gst_tokenize_get_token ($in, $out); &gst_tokenize_verify_token ($in, "netmask", $out); $options{"netmask"} = &gst_tokenize_get_token ($in, $out); &gst_tokenize_verify_token ($in, "{", $out); while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { # printf STDERR "Token $token\n"; if ("option" eq $token) { my (%h1,%h2); my $value = &gst_tokenize_get_token ($in, $out); my $pair = &gst_tokenize_get_token ($in, $out); $pair =~ s/\"//g; # printf STDERR "Value $value\n"; if ($value eq "domain-name-servers") { my $dns_num = 1; $options{"dns1"}=$pair; if ("," eq ($token = &gst_tokenize_get_token ($in, $out))) { $token = &gst_tokenize_get_token ($in, $out); $options{"dns2"}=$token; while (";" ne ($token = &gst_tokenize_get_token ($in, $out))){}; # FIXME # @$in = (pop @$out, @$in); } } elsif (defined &gst_array_find_index (\@known_options, $value)) { # printf STDERR "in array\n"; # remove elements as we read them to avoid duplicates @known_options = grep ($_ ne $value, @known_options); $options{$value}=$pair; &gst_tokenize_verify_token ($in, ";", $out); } elsif (defined &gst_array_find_index (\@ignore_options, $value)) { &gst_tokenize_advance_till ($in, ";", $out); } else { printf STDERR "Unknown option : $value (170)\n"; if ($token ne ";") { &gst_tokenize_advance_till ($in, ";", $out); # &gst_tokenize_verify_token ($in, ";", $out); # @$in = (pop @$out, @$in); } } } elsif ("range" eq $token) { my (%range) = &gst_dhcpd_get_range ($in, $out); my $name = $range{"start"}; $ranges{$name} = \%range; } elsif ("host" eq $token) { my (%host) = &gst_dhcpd_get_host ($in, $out); my $name = $host{"host_name"}; $hosts{$name}= \%host; } elsif ("default-lease-time" eq $token) { $token = &gst_tokenize_get_token ($in, $out); # $options{"default-lease-time"}=$token; $token = &gst_tokenize_get_token ($in, $out); } elsif ("max-lease-time" eq $token) { $token = &gst_tokenize_get_token ($in, $out); # $options{"max-lease-time"}=$token; $token = &gst_tokenize_get_token ($in, $out); } elsif ("}" eq $token) { my $resp; $resp{"options"}=\%options; if (scalar (%ranges)) { $resp{"ranges"}=\%ranges; } if (scalar (%hosts) gt 0) { $resp{"hosts"}=\%hosts; } return %resp; } else { &gst_tokenize_warning ("Unexpected token \"$token\" (205)"); &gst_tokenize_skip_till ($in, ";", $out); } } return undef; } sub gst_dhcpd_get_subnets { my ($fname) = @_; my (%subnets); my $in; my @out_array = ""; my $out = \@out_array; $in = &gst_tokenize ($fname); while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { if ("subnet" eq $token) { my %subnet = &gst_dhcpd_get_subnet ($in, $out); my $options = $subnet{"options"}; my $ip = $$options{"ip"}; $subnets{$ip} = \%subnet; } elsif ("group" eq $token) { &gst_tokenizer_error ("Groups not supported\n"); } elsif ("shared-network" eq $token) { &gst_tokenizer_error ("Shared networks not supported\n"); } } return \%subnets; } ## ------------------------ XML Parsing -------------------------------------- sub gst_dhcpd_xml_parse_host { my ($tree, $hosts) = @_; my (%hash); shift @$tree; while ($elem = shift @$tree) { $hash{$elem} = &gst_xml_get_pcdata (shift @$tree); } $name = $hash{"host_name"}; $$hosts{$name} = \%hash; } sub gst_dhcpd_xml_parse_range { my ($tree, $hosts) = @_; my (%hash); shift @$tree; while ($elem = shift @$tree) { $hash{$elem} = &gst_xml_get_pcdata (shift @$tree); } $name = $hash{"start"}; $$hosts{$name} = \%hash; } sub gst_dhcpd_xml_parse_subnet { my ($tree, $subnets) = @_; my ($elem, %subnet, %options); my (%hosts, %ranges); shift @$tree; while ($elem = shift @$tree) { if ($elem eq "ip") { $options{"ip"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "netmask") { $options{"netmask"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "domain-name") { $options{"domain-name"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "dns1") { $options{"dns1"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "dns2") { $options{"dns2"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "routers") { $options{"routers"} = &gst_xml_get_pcdata (shift @$tree); } elsif ($elem eq "host") { &gst_dhcpd_xml_parse_host (shift @$tree, \%hosts); } elsif ($elem eq "range") { &gst_dhcpd_xml_parse_range (shift @$tree, \%ranges); } else { &gst_report ("xml_unexp_tag", $elem); shift @$tree; } } $$subnet{"options"} = \%options unless scalar keys %options == 0; $$subnet{"hosts"} = \%hosts unless scalar keys %hosts == 0; $$subnet{"ranges"} = \%ranges unless scalar keys %ranges == 0; $name = $options{"ip"}; if (not defined ($name)) { printf STDERR "Fatal error, no ip was provided for a subnet\n"; printf STDERR "Improve error reporting (well, investigate current methods)\n"; exit; } $$subnets{$name} = $subnet; return; } ## ------------------------ Set --------------------------------------------- sub gst_dhcpd_replace_subnets { my ($file, $old_values, $new_subnets_ref) = @_; my (%old_subnets, %new_subnets); my (@remove_list, @add_list, @matching_list); %old_subnets = %{$$old_values{"subnets"}}; %new_subnets = %{$new_subnets_ref}; $matching_list = &gst_dhcpd_subnets_match (\%old_subnets, \%new_subnets); &gst_dhcpd_subnets_match_dump ($matching_list); %m = %$matching_list; foreach $key (keys %m) { $a=$m{$key}; &gst_dhcpd_replace_subnet ($file, $key, $m{$key}); } } sub gst_dhcpd_skip_block { my ($in, $out) = @_; my $token; while (defined ($token = &gst_tokenize_get_token ($in, $out))) { if ($token eq "}") { return; } if ($token eq "{") { &gst_dhcpd_skip_block ($in, $out); } } } sub gst_dhcpd_get_host_comment { my ($out) = @_; my $token, $line, $comment = ""; $line = @$out[-2]; $_ = $line; if (/\s*\#/) { $comment = $'; chomp ($comment); $comment =~ s/\s*//; return $comment; } return undef; } sub gst_dhcpd_add_host { my ($in, $out, $host, $subnet) = @_; my $ws, $ws2; # FIXME, the indentation can be different, found the whitespace neede # for now, hardcode $ws $ws = "\t"; $ws2 = $ws . "\t"; # Add the host &gst_tokenize_append_token ($out, $ws . "\n"); if (defined ($$host {"comment"})) { &gst_tokenize_append_token ($out, $ws . "# " . $$host {"comment"} . "\n"); } &gst_tokenize_append_token ($out, $ws . "host " . $$host {"host_name"} . " {\n"); if (defined ($$host {"mac_address"})) { &gst_tokenize_append_token ($out, $ws2 . "hardware ethernet " . $$host {"mac_address"} . ";\n"); } if (defined (my $ip = $$host {"ip"})) { &gst_tokenize_append_token ($out, $ws2 . "fixed-address " . $$host {"ip"} . ";\n"); } &gst_tokenize_append_token ($out, $ws . "}\n"); } sub gst_dhcpd_replace_host { my ($in, $out, $hosts) = @_; my $hostname; my $new_values; # array that contains the host information $comment = &gst_dhcpd_get_host_comment ($out); $hostname = &gst_tokenize_get_token ($in, $out); $new_values = $$hosts{$hostname}; # Delete this host ? if (not defined $new_values) { &gst_tokenize_undo ($in, $out, 2); # Return the hostname & "host" $_ = @$out[-1]; pop (@$out) if (/\s*\#/); # remove the comment ? $_ = @$out[-1]; pop (@$out) if (/\s*\n$/); # remove the newline ? &gst_tokenize_skip_till ($in, "}"); return; } # Replace the comment (or add it) my $new_comment = $$new_values{"comment"}; if (defined ($comment)) { $$out [-3] =~ s/\#( |)$comment/\# $new_comment/; } elsif (defined $new_comment) { # Insert a comment &gst_tokenize_undo ($in, $out, 2); &gst_tokenize_append_token ($out, "\t# " . $new_comment . "\n"); &gst_tokenize_get_token ($in, $out); &gst_tokenize_get_token ($in, $out); } &gst_tokenize_verify_token ($in, "{", $out); # Flags so that we know if we need to add an element or not my $hardware_ethernet = 0; my $fixed_address = 0; while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { if ("hardware" eq $token) { my $mac = $$new_values{"mac_address"}; if ((defined $mac) && ($mac ne "")) { &gst_tokenize_verify_token ($in, "ethernet", $out); &gst_tokenize_replace_token ($out, "ethernet"); # clean if broken &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, $mac); &gst_tokenize_verify_token ($in, ";", $out); } else { &gst_tokenize_undo ($in, $out, 1); &gst_tokenize_skip_till ($in, ";"); } $hardware_ethernet = 1; } elsif ("fixed-address" eq $token) { my $ip = $$new_values{"ip"}; if ((defined $ip) && ($ip ne "")) { &gst_tokenize_get_token ($in, $out); # discard &gst_tokenize_replace_token ($out, $ip); &gst_tokenize_verify_token ($in, ";", $out); } else { &gst_tokenize_undo ($in, $out, 1); &gst_tokenize_skip_till ($in, ";"); } $fixed_address = 1; } elsif ("}" eq $token) { last; } else { &gst_tokenize_advance_till ($in, ";", $out); } } &gst_tokenize_undo ($in, $out); # Now add items that where not found my $ws = "\t\t"; if (($fixed_address eq 0) && (defined (my $ip = $$new_values {"ip"}))) { &gst_tokenize_append_token ($out, $ws . "fixed-address " . $ip . ";\n"); } if (($hardware_ethernet eq 0) && (defined (my $mac = $$new_values {"mac_address"}))) { &gst_tokenize_append_token ($out, $ws . "hardware ethernet " . $mac . ";\n"); } &gst_tokenize_get_token ($in, $out); delete $$hosts{$hostname}; } sub gst_dhcpd_replace_range { my ($in, $out, $ranges) = @_; my $range; # FIXME, Only support one range for now @ranges_keys = keys %$ranges; $range = $$ranges{@ranges_keys[0]}; if ($$range{"bootp"}) { &gst_tokenize_append_token ($out, " dynamic-bootp"); } &gst_tokenize_append_token ($out, " " . $$range{"start"}); &gst_tokenize_append_token ($out, " " . $$range{"end"} ); &gst_tokenize_append_token ($out, ";\n"); &gst_tokenize_skip_till ($in, ";"); } sub gst_tokenize_replace_dns { my ($in, $out, $options) = @_; my $token; my $dns1 = $$options{"dns1"}; my $dns2 = $$options{"dns2"}; # Remove this item ? if ($dns1 eq undef && $dns2 eq undef) { &gst_tokenize_undo ($in, $out, 2); &gst_tokenize_skip_till ($in, ";"); &gst_tokenize_undo ($in, $out, 1); return; } # # Dns is tricky because the file has a list of servers, our xml has # dns1 and dns2, maybe we should change the xml ? # $token = &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, $dns1); $token = &gst_tokenize_get_token ($in, $out); # If we already have multiple DNS's if ($token eq ",") { $token = &gst_tokenize_get_token ($in, $out); if (defined $dns2) { &gst_tokenize_replace_token ($out, $dns2); &gst_tokenize_verify_token ($in, ";", $out); } else { &gst_tokenize_remove_token ($out, 2); # Remove "," and dns2 &gst_tokenize_skip_till ($in, ";"); &gst_tokenize_append_token ($out, ";\n"); # skip till ; will skip ; } } # If we only had one server before, do we need to add dns2 ? elsif (";" eq $token) { if (defined $dns2) { my $temp = pop (@$out); &gst_tokenize_append_token ($out, ", "); &gst_tokenize_append_token ($out, $dns2); &gst_tokenize_append_token ($out, $temp); } } else { &gst_tokenize_warning ("Unexpected token $token\n"); &gst_tokenize_advance_till ($in, ";", $out); } # Reverse the last token which was ";" so that # we leave it there for _verify ($in, ";",... to pass &gst_tokenize_undo ($in, $out); return; } sub gst_dhcpd_replace_subnet_real { my ($in, $out, $new_values, $subnet) = @_; my $options, $hosts, $ranges; my @ignore_options = qw(time-offset ntp-servers); my @ignore_keywords = qw(default-lease-time max-lease-time); my $range_replaced = 0; $options = $$new_values{"options"}; $hosts = $$new_values{"hosts"}; $ranges = $$new_values{"ranges"}; while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { # printf STDERR "Token :: -->$token<--\n"; if ("option" eq $token) { $token = &gst_tokenize_get_token ($in, $out); # printf STDERR "Option $token\n"; if ("domain-name" eq $token || "nis-domain" eq $token) { $token = &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, "\"" . $$options{"domain-name"} . "\""); } elsif ("routers" eq $token) { $token = &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, $$options{"routers"}); } elsif ("subnet-mask" eq $token) { $token = &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, $$options{"netmask"}); } elsif ("domain-name-servers" eq $token) { &gst_tokenize_replace_dns ($in, $out, $options); } elsif (defined &gst_array_find_index (\@ignore_options, $value)) { &gst_tokenize_advance_till ($in, ";", $out); &gst_tokenize_undo ($in, $out, 1); } else { printf STDERR "--- UNKNOWN option : $token\n"; &gst_tokenize_advance_till ($in, ";", $out); &gst_tokenize_undo ($in, $out, 1); } &gst_tokenize_verify_token ($in, ";", $out); } elsif ("netmask" eq $token) { # This is outside of the `{` $token = &gst_tokenize_get_token ($in, $out); &gst_tokenize_replace_token ($out, $$options{"netmask"}); &gst_tokenize_verify_token ($in, "{", $out); } elsif ("range" eq $token) { if (not $range_replaced) { &gst_dhcpd_replace_range ($in, $out, $ranges); $range_replaced = 1; } } elsif ("host" eq $token) { &gst_dhcpd_replace_host ($in, $out, $hosts); } elsif (defined &gst_array_find_index (\@ignore_keywords, $value)) { &gst_tokenize_advance_till ($in, ";", $out); } elsif ("}" eq $token) { last; } else { printf STDERR "Unrecognized Token :: -- $token (545)\n"; &gst_tokenize_advance_till ($in, ";", $out); } } # What is out, is now our input &gst_tokenize_undo ($in, $out, 1); foreach $key (keys %$hosts) { &gst_dhcpd_add_host ($in, $out, $$hosts{$key}, $subnet); } } sub gst_dhcpd_replace_subnet { my ($file, $subnet, $new_values) = @_; my $in; my @out_array = ""; my $out = \@out_array; $in = &gst_tokenize ($file); &gst_file_buffer_save ($out, $file . ".1"); while (defined (my $token = &gst_tokenize_get_token ($in, $out))) { if ("subnet" eq $token) { $token = &gst_tokenize_get_token ($in, $out); if ($token ne $subnet) { &gst_tokenize_verify_token ($in, "netmask"); $token1 = &gst_tokenize_get_token ($in, $out); # 255.255.255.0 &gst_tokenize_verify_token ($in, "{"); &gst_dhcpd_skip_block ($in, $out); } else { &gst_dhcpd_replace_subnet_real ($in, $out, $new_values, $subnet); } } } # &gst_file_buffer_save ($out, $file . ".new"); &gst_file_buffer_save ($out, $file); } sub gst_dhcpd_subnets_match_dump { my ($ref) = @_; my %matching_list = %$ref; # printf STDERR "\n\nDumping match list\n"; foreach $needle (keys %matching_list) { $a = $matching_list{$needle}; # printf STDERR " Value:$needle Pair:$a\n"; } # printf STDERR "\n\n"; } sub gst_dhcpd_subnets_match { my ($old_subnets_ref, $new_subnets_ref) = @_; my (%matching_list); my (%old_subnets, %new_subnets); %old_subnets = %$old_subnets_ref; %new_subnets = %$new_subnets_ref; $num_old = scalar keys %old_subnets; $num_new = scalar keys %new_subnets; # Here we can write a smart mathing system # For now do any matching for 1 old 1 new to develop # the replacing code # if (($num_old ne 1) || ($num_new) ne 1) { # printf STDERR "Can only replace 1-1\n"; # exit; # } @a = keys %old_subnets; @b = keys %new_subnets; # printf STDERR "B zero is ->" . $b[0] . "<-\n"; $matching_list {$b[0]} = $new_subnets{$b[0]}; return \%matching_list; } ## ------------------------- Parsing tables --------------------------------- sub gst_dhcpd_conf_get_parse_table { my %dist_map = ( "redhat-7.0" => "redhat-7.0", "redhat-7.1" => "redhat-7.0", ); my %dist_tables = ( "redhat-7.0" => { fn => { DHCPD_CONF => "/etc/dhcpd.conf", DHCPD_SERVICE => "dhcpd", }, table => [ [ "active", \&gst_service_sysv_get_status, DHCPD_SERVICE ], [ "installed", \&gst_service_sysv_installed, DHCPD_SERVICE ], [ "configured", \&gst_file_exists, DHCPD_CONF ], [ "subnets", \&gst_dhcpd_get_subnets, DHCPD_CONF], ] } ); my $dist = $dist_map {$gst_dist}; return %{$dist_tables{$dist}} if $dist; &gst_report ("platform_no_table", $gst_dist); return undef; } sub gst_dhcpd_conf_get { my %dist_attrib; my $hash; %dist_attrib = &gst_dhcpd_conf_get_parse_table (); $hash = &gst_parse_from_table ($dist_attrib{"fn"}, $dist_attrib{"table"}); return $hash; } sub gst_dhcpd_conf_get_replace_table { my %dist_map = ( "redhat-7.0" => "redhat-7.0", "redhat-7.1" => "redhat-7.0", ); my %dist_tables = ( "redhat-7.0" => { fn => { DHCPD_CONF => "/etc/dhcpd.conf", DHCPD_SERVICE => "dhcpd", }, table => [ [ "subnets", \&gst_dhcpd_replace_subnets, [DHCPD_CONF, OLD_HASH] ], # [ "active", \&gst_service_sysv_set_status, [65, DHCPD_SERVICE] ] ] }, ); my $dist = $dist_map {$gst_dist}; return %{$dist_tables{$dist}} if $dist; &gst_report ("platform_no_table", $gst_dist); return undef; } sub gst_dhcpd_conf_set { my ($values_hash) = @_; my (%dist_attrib, $old_hash); my $res; %dist_attrib = &gst_dhcpd_conf_get_replace_table (); $old_hash = &gst_dhcpd_conf_get (); $res = &gst_replace_from_table ($dist_attrib{"fn"}, $dist_attrib{"table"}, $values_hash, $old_hash); return $res; } 1;