#!/usr/bin/perl -w use strict; use lib '/usr/local/myNMS/bin' ; use My_Utils ; use My_DButils ; use My_Config ; my $VERSION = '7.2.2' ; =pod Copyright (c) 2000-2001 John Stumbles and the University of Reading. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Scripts which gather info from NIS maps (and quotas) (using remote command on 'main' as relevant data is not available directly on NMS) to maintain DB tables. C H A N G E H I S T O R Y =========================== 7.2.2 20Feb01 removed $CONFIG{DIR}{BASE} from myNMS subdir specs 7.2.1 14Feb01 get more config params from My_COnfig, changed functions for getting maps to diff_map, maps_differ and update_mapfile fixed error in update_db2char causing failure on attempt to insert record with duplicate unique key field 7.2 30Jan01 use Modules changed location of files mirroring NIS maps etc to var (CONFIG{DIR}{VAR}) Moved LOG_EVENT into My_DButils =cut my $DB = 'NMS'; my $dbh = DB_init($DB) ; # yukky, this is a copy of the database handle created in DB_utils ... my ($PROGNAME)=$0=~/(?:.*\/)?([^\/]+)/; sub WARNING { print STDERR "$PROGNAME ($0): " ; printf STDERR @_ ; } sub INFORM { printf "$PROGNAME at %s: ", YYYY_MM_DD_hh_mm_ss(time()); printf @_ ; } INFORM "($0) ver $VERSION\n" ; my (%netgroup_of_hosts, %netgroups_by_host, %netgroup_of_users, %netgroups_by_user, %exports_by_host) ; die "CONFIG{DIR}{VAR} not set\n" unless $CONFIG{DIR}{VAR} ; die "CONFIG{PROG}{REMOTE_SHELL} not set\n" unless $CONFIG{PROG}{REMOTE_SHELL} ; die "CONFIG{MAP}{HOST} not set\n" unless $CONFIG{MAP}{HOST} ; die "CONFIG{MAP}{USER} not set\n" unless $CONFIG{MAP}{USER} ; die "CONFIG{MAP}{NISCAT} not set\n" unless $CONFIG{MAP}{NISCAT} ; die "CONFIG{MAP}{QUOTAS} not set\n" unless $CONFIG{MAP}{QUOTAS} ; die "CONFIG{MAP}{QUOTA_FILESPEC} not set\n" unless $CONFIG{MAP}{QUOTA_FILESPEC} ; die "CONFIG{MAP}{SHOWMOUNT} not set\n" unless $CONFIG{MAP}{SHOWMOUNT} ; die "CONFIG{MAP}{SHOWMOUNT_MIN_SIZE} not set\n" unless $CONFIG{MAP}{SHOWMOUNT_MIN_SIZE} ; my $VAR_DIR = $CONFIG{DIR}{VAR} ; my $USAGE = qq(USAGE: $PROGNAME --hosts | --passwd | --email | --netgroups | --quotas | --help ); ############################################################################## if ($_ = shift) { if (/^--quotas/i) { update_quotas(); exit; } if (/^--hosts/i) { update_hosts(); exit; } if (/^--passwd/i) { update_passwd(); exit; } if (/^--netgroup/i) { update_netgroup(); exit; } if (/^--email/i) { update_emailadds(); exit; } die "$USAGE\n"; } update_hosts() ; update_passwd() ; update_emailadds(); update_netgroup() ; update_quotas() ; ############################################################################## sub update_quotas { my $FUNCNAME = 'update_quotas' ; my $rcmd = qq($CONFIG{PROG}{REMOTE_SHELL} $CONFIG{MAP}{HOST} -l $CONFIG{MAP}{USER} "$CONFIG{MAP}{QUOTAS} '$CONFIG{MAP}{QUOTA_FILESPEC}'; echo __DONE__" |); my $SOURCE = "$rcmd"; my $END_TOKEN = '__DONE__' ; # string expected as last record from source return unless open (SRC, $SOURCE) ; my @source = () ; close SRC; unless (scalar @source) { WARNING "*** could not get quotas info from '$SOURCE'\n"; return ; } unless (shift (@source) =~ /^#VERSION\t(\d.*)$/) { WARNING "*** did not get VERSION number from '$SOURCE'\n"; return ; } unless (pop (@source) =~ /^$END_TOKEN$/) { WARNING "*** did not get '$END_TOKEN' from '$SOURCE'\n"; return ; } update_quotas_DB (@source) ; } ############################################################################## sub update_quotas_DB # given an array of records from the quotas-file-interrogation program run on another machine, # update the 'quotas' table in the NMS database. # Assume we're only interested in quota info for uids which exist in the passwd table # Some quotas file may be unreadable to our interrogation program: # For uids where quotas file is readable but for which we get no record assume quota # is not applied and make fields 0 # For uids whose quota files are unreadable, or whose home directories are on partitions # from which we have not tried to read quota info, we could either set all fileds values NULL # or make sure we have no records (which will return NULL fields if we do a LEFT JOIN on passwd + quota) { my (@quotas) = @_ ; return unless scalar @quotas ; my %home ; # table of home dirs from passwd table { my $sth=$dbh->prepare('SELECT uid, home FROM passwd'); $sth->execute(); if ($sth->rows) { my ($uid, $home); $sth->bind_columns(undef, \($uid, $home)) ; while ($sth->fetch()) { $home{$uid}=$home; } } } my %quota ; # hash table of quota records by uid my %part_readable ; # table of partitions and whether they are readable my $part ; # current partition foreach (@quotas) { if (/^([1-9]\d+)\t(\d+.*\d)$/) # quota record: ignore root (uid 0) { my $uid = $1 ; my $q = $2 ; die "*** ERROR got quota record before any partition name\n" unless $part ; unless ($home{$uid}) # ignore quotas for uids not (|no longer) in passwd { next ; } # (else) unless ($home{$uid} =~ m|^$part|) # ignore quotas on partition other than where home directory is { next ; } my ($bhard, $bsoft, $bcurr, $fhard, $fsoft, $fcurr, $btime, $ftime) = split (/\t/, $q) ; $quota{$uid}{curr} = { bhard => int($bhard/2), bsoft => int($bsoft/2), bcurr => int($bcurr/2), btime => $btime, fhard => $fhard, fsoft => $fsoft, fcurr => $fcurr, ftime => $ftime, } ; next ; } if (m|^(/\S+/)quotas|) # /part/ition/quotas filespec { $part = $1 ; $part_readable{$part}=1; # assume partition is readable unless proved otherwise next ; } if (/Permission denied/) # quotas file unreadable for this partition { die "*** ERROR got 'permission denied' record before any partition name\n" unless $part ; $part_readable{$part} = 0 ; # unreadable next ; } } # get list of users on partitions we have seen and mark their quota records according to whether partition is readable { #(scope) foreach my $part (keys %part_readable) { next unless $part_readable{$part} ; # skip unreadable partitions my $sth = $dbh->prepare("SELECT uid FROM passwd WHERE home LIKE '$part%'") ; $sth->execute(); if ($sth->rows) { my ($uid) ; $sth->bind_columns(undef, \($uid)) ; while ($sth->fetch) { unless ($quota{$uid}{curr}) # unless we have a (non-zero) record for this uid make a no quota record { $quota{$uid}{curr} = { bhard => 0, bsoft => 0, bcurr => 0, btime => 0, fhard => 0, fsoft => 0, fcurr => 0, ftime => 0, } ; } } } } } # (scope) # get current DB records: { #(scope) my $SELECT = "SELECT uid, bhard, bsoft, bcurr, fhard, fsoft, fcurr, btime, ftime, ctime FROM quotas" ; my $sth=$dbh->prepare($SELECT); $sth->execute(); if ($sth->rows) { my ($uid, $bhard, $bsoft, $bcurr, $fhard, $fsoft, $fcurr, $btime, $ftime, $mtime); $sth->bind_columns(undef, \($uid, $bhard, $bsoft, $bcurr, $fhard, $fsoft, $fcurr, $btime, $ftime, $mtime)) ; while ($sth->fetch()) { $quota{$uid}{DB} = { bhard => $bhard, bsoft => $bsoft, bcurr => $bcurr, btime => $btime, fhard => $fhard, fsoft => $fsoft, fcurr => $fcurr, ftime => $ftime, mtime => $mtime, } ; } } } #(scope) foreach my $uid (keys %quota) { # check for uids no longer in passwd (these should only come about where a uid has disappeared from passwd, # as we filtered out non-passwd uids in quotas when we process quotas data earlier ... but check anyway) unless ($home{$uid}) { if ($quota{$uid}{DB}) { Log_Event "uid $uid in quotas DB is no longer in passwd: deleting from database" ; my $DO = "DELETE FROM quotas WHERE uid=$uid"; $dbh->do($DO); } delete $quota{$uid} ; # delete from hash table too next ; # no more to do with this uid } # check whether this uid's partition is readable unless ($quota{$uid}{curr}) # (curr record == readable partition) { delete $quota{$uid} ; # delete from hash table too next ; # no more to do with this uid } # compare current and previous quota info: if ( ($quota{$uid}{DB}) # do we have both current (checked earlier) and previous records, and are they the same? and ($quota{$uid}{curr}{bhard} == $quota{$uid}{DB}{bhard}) and ($quota{$uid}{curr}{bsoft} == $quota{$uid}{DB}{bsoft}) and ($quota{$uid}{curr}{bcurr} == $quota{$uid}{DB}{bcurr}) and ($quota{$uid}{curr}{fhard} == $quota{$uid}{DB}{fhard}) and ($quota{$uid}{curr}{fsoft} == $quota{$uid}{DB}{fsoft}) and ($quota{$uid}{curr}{fcurr} == $quota{$uid}{DB}{fcurr}) and ($quota{$uid}{curr}{btime} == $quota{$uid}{DB}{btime}) and ($quota{$uid}{curr}{ftime} == $quota{$uid}{DB}{ftime}) ) { delete $quota{$uid} ; next ; # nothing to do for this uid } # (else) this user's quota record has changed somehow (including appearing for first time) my $mtime = 0 ; if ($quota{$uid}{DB}) # if we have a DB record: { # check for user going over or back below quota ((b|f)time > 0 means over quota) # XOR values (Camel 94) to determine if either blocks of files quota status has changed: # unless either (block|file) quota status has changed keep existing value for 'last change' time unless ( ( !$quota{$uid}{curr}{btime} ^ !$quota{$uid}{DB}{btime} ) or ( !$quota{$uid}{curr}{ftime} ^ !$quota{$uid}{DB}{ftime} )) { $mtime = $quota{$uid}{DB}{mtime} ; } else # something changed: set new value { $mtime = time() ; } } my $DO = sprintf qq(REPLACE INTO quotas (uid,bhard,bsoft,bcurr,fhard,fsoft,fcurr,btime,ftime,ctime) VALUES (%u,%u,%u,%u,%u,%u,%u,%u,%u,%u)), $uid, $quota{$uid}{curr}{bhard}, $quota{$uid}{curr}{bsoft}, $quota{$uid}{curr}{bcurr}, $quota{$uid}{curr}{fhard}, $quota{$uid}{curr}{fsoft}, $quota{$uid}{curr}{fcurr}, $quota{$uid}{curr}{btime}, $quota{$uid}{curr}{ftime}, $mtime ; $dbh->do($DO); } # (foreach uid) } ############################################################################## sub update_emailadds { my $map_name = 'email_addresses'; my $file_name = "$VAR_DIR/$map_name" ; my @data = diff_map($map_name, $file_name) ; my $FUNCNAME = 'update_emailadds' ; unless (scalar @data ) { INFORM "$FUNCNAME: nothing to do\n"; return; } INFORM "$FUNCNAME: updating DB\n"; if (update_emailadds_DB (@data)) { update_mapfile($file_name, @data) ; } } ############################################################################## sub update_emailadds_DB { my (@data) = @_ ; return 0 unless scalar @data ; my %emailadds; # get DB table: my %PW = getall_passwd() ; my $Table='passwd'; foreach (@data) { next unless /:$/; # line must end in : my ($canonical, $recipient, @synonyms) = split /:/ ; next unless $recipient ; next unless $canonical ; # skip broken records! pop @synonyms; # last will always be null # now check: we aren't interested in email_addresses table per se but just # in getting email addresses for users, so we need to check whether 'recipient' is # a username in our passwd table, or it may be a synonym my $username = '' ; my ($email_address, $email_aliases) ; if ($PW{$recipient}) # recipient == username ? { $username = $recipient ; $email_address = $canonical ; $email_aliases = '' . (join ' ', @synonyms) ; } else # recipient != username { my $syn; foreach $syn (@synonyms) { next unless $PW{$syn} ; # only get here if we found username $username = $syn; $email_address = "$canonical (-> $recipient)" , $email_aliases = '' . (join ' ', @synonyms) ; # should take out $syn but wtf ... } } # now we have a username if we found a match of recipient or synonym: if ($username) { # check: are email_fields in PW same as here? if (!( (($PW{$username}{'email_address'} or '') eq $email_address ) and (($PW{$username}{'email_aliases'} or '') eq $email_aliases ) )) { # different my $REPLACE = sprintf "REPLACE INTO $Table (username, pwd, pwd_ctime, uid, gid, full_name, home, shell, email_address, email_aliases) VALUES ( '%s','%s', %u, %u, %u, %s, '%s', '%s', %s, %s)" , $username, ($PW{$username}{'pwd'} or ''), ($PW{$username}{'pwd_ctime'} or 0), ($PW{$username}{'uid'} or 0), ($PW{$username}{'gid'} or 0), $dbh->quote($PW{$username}{'full_name'} or ''), ($PW{$username}{'home'} or ''), ($PW{$username}{'shell'} or ''), $dbh->quote( $email_address ), $dbh->quote( $email_aliases ) ; $dbh->do($REPLACE); } } } return 1 ; # OK } ############################################################################## sub getall_passwd # return entire passwd map as hash on username { # get DB table: my $Table='passwd'; my $Fields = 'username, pwd, pwd_ctime, uid, gid, full_name, home, shell, email_address, email_aliases'; my $sth = $dbh->prepare("SELECT $Fields FROM $Table") ; $sth->execute ; my (%PWD, $username, $pwd, $pwd_ctime, $uid, $gid, $full_name, $home, $shell, $email_address, $email_aliases) ; $sth->bind_columns(undef, \($username, $pwd, $pwd_ctime, $uid, $gid, $full_name, $home, $shell, $email_address, $email_aliases)) ; while ($sth->fetch) { %{$PWD{$username}} = ( pwd => $pwd, pwd_ctime => $pwd_ctime, uid => $uid, gid => $gid, full_name => $full_name, home => $home, shell => $shell, email_address => $email_address, email_aliases => $email_aliases ) ; } return %PWD ; } ############################################################################## sub update_hosts { my $map_name = 'hosts.byaddr' ; my $file_name = "$VAR_DIR/$map_name" ; my @data = diff_map($map_name, $file_name) ; my $FUNCNAME = 'update_hosts' ; unless (scalar @data) { INFORM "$FUNCNAME: nothing to do\n"; return; } INFORM "$FUNCNAME: updating DB\n"; my (@new_map, $IPadd, $IPnames, $IPname, %IP_hosts, %IP_hostnames) ; foreach (@data) { push @new_map, $_ ; # save unchomped lines for update_mapfile chomp ; next unless (/^(\d+\.\d+\.\d+\.\d+)\s+([^#]+)/) ; $IPadd = $1 ; $IPnames = $2 ; # IPnames is a string of name + aliases of host $IPnames =~ s/\s*$//; # remove trailing space $IPnames =~ s/\s+/ /g; # turn multiple spaces separating names into single spaces $IP_hostnames{$IPadd}{$IPnames} = defined ; foreach $IPname (split /\s/, $IPnames) { $IP_hosts{$IPadd}{$IPname} = defined ; } } update_db2char('IP_hosts', 'IPadd', 'name', \%IP_hosts) ; update_db2char('IP_hostnames', 'IPadd', 'names', \%IP_hostnames) ; update_mapfile($file_name, @new_map) ; } ############################################################################## sub update_passwd { my $map_name = 'passwd'; my $file_name = "$VAR_DIR/$map_name" ; my @data = diff_map($map_name, $file_name) ; my $FUNCNAME = 'update_passwd' ; unless (scalar @data) # passwd map has not changed from file version { INFORM "$FUNCNAME: nothing to do\n"; return; } INFORM "$FUNCNAME: updating DB\n"; if (update_passwd_DB (@data)) { update_mapfile($file_name, @data) ; } } ############################################################################## sub update_passwd_DB # given array of lines from NIS passwd map, update database { my (@passwd) = @_ ; return 0 unless scalar @passwd ; my (%NIS_passwd, $username, $pwd, $uid, $gid, $full_name, $home, $shell) ; foreach (@passwd) { chomp ; ($username, $pwd, $uid, $gid, $full_name, $home, $shell) = split /:/ ; %{$NIS_passwd{$username}} = ( pwd => $pwd, pwd_ctime => 0, uid => $uid, gid => $gid, full_name => $full_name, home => $home, shell => $shell ); } # get DB table: my $Table='passwd'; my $Fields_pwd = 'username, pwd, pwd_ctime, uid, gid, full_name, home, shell'; my $Fields_email = 'email_address, email_aliases'; my $sth = $dbh->prepare("SELECT $Fields_pwd, $Fields_email FROM $Table") ; $sth->execute ; # re-use variables from before, also need: my ($pwd_ctime, $email_address, $email_aliases) ; $sth->bind_columns(undef, \($username, $pwd, $pwd_ctime, $uid, $gid, $full_name, $home, $shell, $email_address, $email_aliases)) ; while ($sth->fetch) { # does this record exist in NIS? if ($NIS_passwd{$username}) { # exists in both; is it the same (ignoring password hash)? if ( ($NIS_passwd{$username}{uid} == $uid ) and ($NIS_passwd{$username}{gid} == $gid ) and ($NIS_passwd{$username}{full_name} eq $full_name ) and ($NIS_passwd{$username}{home} eq $home ) and ($NIS_passwd{$username}{shell} eq $shell ) ) # same { # other fields the same, has passwd changed? if ($NIS_passwd{$username}{pwd} eq $pwd ) { delete $NIS_passwd{$username} ; # same as DB, no need for this any more } else # password has changed { $NIS_passwd{$username}{pwd_ctime} = time() ; # set password change time to time now } } else # if different, keep email_address info from DB { $NIS_passwd{$username}{email_address} = $email_address ; $NIS_passwd{$username}{email_aliases} = $email_aliases ; # alternatively, we could UPDATE these records at this point, # but it is simpler to do one block of REPLACE later ... } } else # not in NIS any more - delete from DB { $dbh->do("DELETE FROM $Table WHERE username='$username'") ; } } # all in %NIS_passwd now are either absent from or different in DB: foreach $username (keys %NIS_passwd) { my $REPLACE = sprintf "REPLACE INTO $Table VALUES ('$username', '%s', %u, %u, %u, %s, '%s', '%s', %s, %s)" , $NIS_passwd{$username}{pwd}, $NIS_passwd{$username}{pwd_ctime}, $NIS_passwd{$username}{uid}, $NIS_passwd{$username}{gid}, $dbh->quote( $NIS_passwd{$username}{full_name} ), $NIS_passwd{$username}{home}, $NIS_passwd{$username}{shell}, $dbh->quote( $NIS_passwd{$username}{email_address} ), $dbh->quote( $NIS_passwd{$username}{email_aliases} ) ; $dbh->do($REPLACE); } return 1 ; # OK } ############################################################################## sub update_netgroup { my $netgroup_file = "$VAR_DIR/netgroup" ; my @netgroup = diff_map('-k netgroup', $netgroup_file) ; my $FUNCNAME = 'update_netgroup' ; unless (scalar @netgroup) { INFORM "$FUNCNAME: nothing to do\n"; return; } # showmount: give command to get data and sanity check minimum size of table my @sm = get_datasource("$CONFIG{MAP}{SHOWMOUNT} |", $CONFIG{MAP}{SHOWMOUNT_MIN_SIZE}) ; unless (scalar (@sm)) { INFORM "$FUNCNAME: *** error *** could not get showmount table\n"; return ; } INFORM "$FUNCNAME: updating DB\n"; my $netgroup_of_hosts_file = "$VAR_DIR/$CONFIG{FILE}{NETGROUP_OF_HOSTS}"; my $netgroup_of_users_file = "$VAR_DIR/$CONFIG{FILE}{NETGROUP_OF_USERS}"; my $netgroups_by_host_file = "$VAR_DIR/$CONFIG{FILE}{NETGROUPS_BY_HOST}"; my $netgroups_by_user_file = "$VAR_DIR/$CONFIG{FILE}{NETGROUPS_BY_USER}"; my $netgroup_errors_file = "$VAR_DIR/$CONFIG{FILE}{NETGROUP_ERRORS}"; my $exports_file = "$VAR_DIR/$CONFIG{FILE}{EXPORTS}"; my $exports_by_host_file = "$VAR_DIR/$CONFIG{FILE}{EXPORTS_BY_HOST}"; open (ERR, ">$netgroup_errors_file.TMP") || WARNING "Could not open $netgroup_errors_file.TMP" ; my $timestamp = localtime ; print ERR "Errors and inconsistencies seen in netgroups and sufs1 exports\nupdated $timestamp\n\n"; # read the netgroup table, parsing netgroup records which are like: # groupname1 (host1,user1,domain1) (h2,u2,d2) (h3,u3,d3) ... etc # groupname2 (... etc # may find # or \ in place of a (h,u,d) # or comments starting # ... # or groups of groups: # groupofgroup1 group1 group2 group3 group4 group5 ... etc # groupofgroup2 group .... my %netgroup_of_groups ; my %netgroup_hosts; # group-hostname pairs my %netgroup_users; # group-username pairs my %export_hosts; # data structure to hold export-host pairs my @new_map ; foreach (@netgroup) { push @new_map, $_ ; # save unchomped lines for update_NISfile next if /^\s*#/ ; chomp ; my ($group,@members) = (split /\s+/) ; next unless $group; next unless (scalar (@members)) ; if (/\s+\(/) # distinguish group-of-group records from ordinary ones { my $member; foreach $member (@members) { next if $member =~ /[#\\]/ ; $member =~ s/[\(\)]//g; # remove brackets (host,user,domain) from group: my ($host,$user,$domain)=split /\,/, $member; if ($user and ($user ne '-')) { push @{$netgroups_by_user{$user}}, $group; push @{$netgroup_of_users{$group}}, $user; $netgroup_users{$group}{$user}=1 ; } if ($host and ($host ne '-')) { push @{$netgroups_by_host{$host}}, $group ; push @{$netgroup_of_hosts{$group}}, $host; $netgroup_hosts{$group}{$host}=1 ; } } } else # group-of-group record { my $member; foreach $member (@members) { next if $member =~ /#/ ; push @{$netgroup_of_groups{$group}}, $member; } } } printf ERR "\nfrom netgroups: the following groups refer to groups or hosts which do not exist in the hosts or netgroup tables\n%-20s : %s\n", 'group', 'refers to' ; # expand groups-of-groups into individual members my $gog ; foreach $gog (keys %netgroup_of_groups) # for each group-of-groups ... (e.g. staffpcs) { my $gogm ; foreach $gogm (@{$netgroup_of_groups{$gog}}) # for each member of group-of-groups ... (e.g. aahosts) { if (defined $netgroup_of_hosts{$gogm}) # if member of g-o-g is a netgroup of hosts { # add hosts in this group (e.g. aapc1 aapc2 ...) to new group-of-hosts (e.g. staffpcs) push @{$netgroup_of_hosts{$gog}}, @{$netgroup_of_hosts{$gogm}} ; my $host ; # and add g-o-g (e.g. staffpcs) to list of groups of which host is a member foreach $host (@{$netgroup_of_hosts{$gogm}}) { push @{$netgroups_by_host{$host}}, $gog ; $netgroup_hosts{$gog}{$host}=1 ; } } elsif (defined $netgroups_by_user{$gogm}) # if member of g-o-g is member of a netgroup of users { push @{$netgroup_of_users{$gog}}, @{$netgroup_of_users{$gogm}} ; # do as for hosts ** NB untested: we haven't any g-o-g-of-users here! my $user ; foreach $user (@{$netgroup_of_users{$gogm}}) { push @{$netgroups_by_user{$user}}, $gog ; $netgroup_users{$gog}{$user}=1 ; } } else { printf ERR "%-20s : %s\n", $gog, $gogm ; } # add group-of-groups to netgroups: } } # update database: update_db2char ('netgroup_hosts', 'groupname', 'hostname', \%netgroup_hosts) ; update_db2char ('netgroup_users', 'groupname', 'username', \%netgroup_users) ; # print reports: my $x ; open (GoH, ">$netgroup_of_hosts_file.TMP") || WARNING "Could not open $netgroup_of_hosts_file.TMP" ; print GoH "netgroup of hosts\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing netgroup_of_hosts to file $netgroup_of_hosts_file.TMP\n" ; foreach $x (sort keys %netgroup_of_hosts) { printf GoH "%-10s : %s\n", $x, join (' ', @{$netgroup_of_hosts{$x}}) ; } close GoH ; open (GoU, ">$netgroup_of_users_file.TMP") || WARNING "Could not open $netgroup_of_users_file.TMP" ; print GoU "netgroup of users\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing netgroup_of_users to file $netgroup_of_users_file.TMP\n" ; foreach $x (sort keys %netgroup_of_users) { printf GoU "%-20s : %s\n", $x, join (' ', @{$netgroup_of_users{$x}}) ; } close GoU ; open (GbH, ">$netgroups_by_host_file.TMP") || WARNING "Could not open $netgroups_by_host_file.TMP" ; print GbH "netgroups by host\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing netgroups_by_host to file $netgroups_by_host_file.TMP\n" ; foreach $x (sort keys %netgroups_by_host) { printf GbH "%-20s : %s\n", $x, join (' ', @{$netgroups_by_host{$x}}) ; } close GbH ; open (GbU, ">$netgroups_by_user_file.TMP") || WARNING "Could not open $netgroups_by_user_file.TMP" ; print GbU "netgroups by user\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing netgroups_by_user to file $netgroups_by_user_file.TMP\n" ; foreach $x (sort keys %netgroups_by_user) { printf GbU "%-10s : %s\n", $x, join (' ', @{$netgroups_by_user{$x}}) ; } close GbU ; my (%showmount) ; # read the showmount table, parsing netgroup records which are like: # export list for sufs1: # /file/system1 name1,name2,name3,name4,... # /file/system2 (everyone) # # where name1,name2 etc may be host names or names of netgroups of hosts foreach (@sm) { chomp ; next unless (m|^(/\S+)\s+(.*)|) ; @{$showmount{$1}} = split(/\,/, $2) ; } open (X, ">$exports_file.TMP") || WARNING "Could not open $exports_file.TMP" ; print X "sufs1: exports\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing exports to file $exports_file.TMP\n" ; open (XbH, ">$exports_by_host_file.TMP") || WARNING "Could not open $exports_by_host_file.TMP" ; print XbH "sufs1: exports by host\nupdated $timestamp\n\n"; # INFORM "$FUNCNAME: Writing exports by host to file $exports_by_host_file.TMP\n" ; printf ERR "\nfrom sufs1 export list: the following filesystems are exported to hosts or groups which do not exist in the hosts or netgroup tables\n%-30s : %s\n", 'export', 'exported to' ; my @IP_names = IPhostnames() ; # build table of which filesystems are exported to each host: my $export ; foreach $export (sort keys %showmount) { print X "\n$export\n"; my $mountee ; foreach $mountee (@{$showmount{$export}}) { printf X "\t%-20s\t# ", $mountee; my @matching_hosts ; # determine if name is (everyone), name of host, or name of netgroup if ($mountee eq '(everyone)') { @matching_hosts = @IP_names ; print X "all hosts\n" ; } elsif (defined $netgroup_of_hosts{$mountee}) { @matching_hosts = @{$netgroup_of_hosts{$mountee}} ; printf X "netgroup - expands to:\n\t\t%s\n", join ("\t", @matching_hosts) ; } elsif (scalar(grep ( /$mountee/ , @IP_names) )) # is name in hosts? { @matching_hosts = ($mountee) ; # just add host name print X "host\n" ; } else { printf ERR "%-30s : %s\n", $export, $mountee ; print X "*** not found in hosts or netgroups ***\n" ; next ; # skip so don't do next bit ... } # now add each in matching hosts list to export list my $host ; foreach $host (@matching_hosts) { push @{$exports_by_host{$host}}, $export ; $export_hosts{$host}{$export} = 1; } } } close X; update_db2char ('export_hosts', 'hostname', 'filesystem', \%export_hosts) ; foreach $x (sort keys %exports_by_host) { printf XbH "\n%-20s\n\t%s\n",$x,join( "\n\t", @{$exports_by_host{$x}}); } close XbH; # close error log file close ERR ; # rename .TMP files # INFORM "$FUNCNAME: Renaming .TMP files\n"; rename ( "$netgroup_of_hosts_file.TMP", $netgroup_of_hosts_file ); rename ( "$netgroup_of_users_file.TMP", $netgroup_of_users_file ); rename ( "$netgroups_by_host_file.TMP", $netgroups_by_host_file ); rename ( "$netgroups_by_user_file.TMP", $netgroups_by_user_file ); rename ( "$netgroup_errors_file.TMP", $netgroup_errors_file) ; rename ( "$exports_file.TMP", $exports_file) ; rename ( "$exports_by_host_file.TMP", $exports_by_host_file) ; update_mapfile($netgroup_file, @new_map) ; } ############################################################################## sub update_db2char # update database of 2 char fields from a data structure { my ($Table, $field1, $field2, $struct) = @_; my $FUNCNAME = 'update_db2char' ; # INFORM "$FUNCNAME: updating $Table table\n" ; # read table from db, comparing with new table # where record in new table does not exist in db create (insert) it into db # where record in db does not exist in new table delete it from db my $sth = $dbh->prepare("SELECT $field1, $field2 FROM $Table") ; $sth->execute ; my ($db_field1, $db_field2, %db_exist, @DO) ; $sth->bind_columns(undef, \($db_field1, $db_field2)) ; while ($sth->fetch) { # does this record from db exist in struct ? if ($struct->{$db_field1}{$db_field2}) # got same { $db_exist{$db_field1}{$db_field2}=defined ; } else { ##INFORM "$FUNCNAME: delete:\t%s\t%s\n", $db_field1, $db_field2 ; push @DO, "DELETE FROM $Table WHERE $field1='$db_field1' AND $field2='$db_field2'" ; } } $sth->finish ; my ($f1, $f2) ; foreach $f1 (keys %{$struct}) { foreach $f2 (keys %{$$struct{$f1}}) { if (defined $db_exist{$f1}{$f2}) { } else { ##INFORM "$FUNCNAME: create:\t%s\t%s\n", $f1, $f2 ; push @DO, "INSERT INTO $Table VALUES ('$f1','$f2')" ; } } } # create and delete from database foreach my $DO (@DO) { $dbh->do($DO) ; } } ############################################################################## sub diff_map # get a NIS map and diff it with file of last version; # update file and return array of new map if different { my $map = shift or return () ; my $file = shift ; # optional my $rcmd = "$CONFIG{PROG}{REMOTE_SHELL} $CONFIG{MAP}{HOST} -l $CONFIG{MAP}{USER} '$CONFIG{MAP}{NISCAT} $map; echo __DONE__' |"; unless (open (SRC, $rcmd)) { INFORM "*** COULD NOT OPEN MAP: $map DATA SOURCE: $rcmd - $!" ; return () ; } my @map = () ; close SRC; unless (scalar (@map)) { INFORM "*** GOT EMPTY MAP: $map ***\n" ; return () ; } unless (pop (@map) =~ /^__DONE__$/) { INFORM "*** GOT INCOMPLETE MAP: $map ***\n" ; return () ; } return @map unless defined $file ; if (maps_differ($file, @map)) { return @map ; } return () ; } ############################################################################## sub maps_differ { my ($file, @map) = @_ ; return 1 unless scalar @map ; return 1 unless open (FILE, $file) ; my @file ; while () # read file into array, { next if /^## UPDATED:/; # ignoring timestamp push @file, $_ ; } return 1 unless (scalar @file eq scalar @map) ; # check list sizes my ($f, $map) ; while ($f = shift @file, $map = shift @map) { return 1 unless ($f eq $map) ; } return 0 ; } ############################################################################## sub update_mapfile # updates the file used by maps_differ to compare with new map { my ($file, @map) = @_ ; return unless scalar @map ; return unless (open (FILE, "> $file")) ; printf FILE "## UPDATED: %s\n", nameDate() ; print FILE @map ; close FILE ; } ############################################################################## sub get_datasource { my ($source, $check_size) = @_ ; unless (open (SRC, $source)) { WARNING "failed to open data source [$source]\n" ; return () ; } my @source = () ; if (scalar(@source) < $check_size) { WARNING "failed to get data from [$source]\n" ; return () ; } return @source; } ##############################################################################