#!/usr/bin/perl -w # # vim: ts=4 ft=perl # # Copyright (c) 2002 Will Andrews # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $CSociety: ldap/ldap_update,v 1.8 2003/05/02 04:57:46 will Exp $ use strict; use Getopt::Std; use Net::LDAP; use Sys::Syslog; my %config; my %opts; my $ldap; my $userdir; my $grpdir; my @uexist; my @gexist; my @u; my @g; my @add_users; my @del_users; my @add_groups; my @del_groups; my %gmembers; my %gid; my @varnames = ( "ldap_host", "ldap_port", "ldap_version", "bind_dn", "bind_pass", "debug", "user_filter", "grp_filter", "scope", "nologin", "master_pwd", "master_pwd_head", "master_pwd_re", "master_grp", "master_grp_head", "master_grp_re", "noname", "pwd_mkdb_opts", "get_pass", "base_dn", "user_acl", "group_acl", ); getopts('d:f:h:D:', \%opts); # Set defaults for the config options. $config{ldap_host} = "127.0.0.1"; $config{bind_dn} = "dc=fake,dc=dom"; $config{conf_file} = "/etc/ldap_update.conf"; $config{ldap_version} = 3; $config{bind_pass} = ""; $config{debug} = 0; $config{user_filter} = "(objectclass=posixAccount)"; $config{grp_filter} = "(objectclass=posixGroup)"; $config{scope} = "subtree"; $config{nologin} = "/sbin/nologin"; $config{master_pwd} = "/etc/master.passwd"; $config{master_pwd_head} = "/etc/master.passwd.head"; $config{master_pwd_re} = ".*:.*:.*:.*::.*"; $config{master_grp} = "/etc/group"; $config{master_grp_head} = "/etc/group.head"; $config{master_grp_re} = "^.*:.*:.*:[^:]*"; $config{noname} = "Unknown"; $config{pwd_mkdb_opts} = "-p"; $config{get_pass} = 0; $config{base_dn} = "dc=fake,dc=dom"; $config{user_acl} = ""; $config{group_acl} = ""; # Main script body. BEGIN: { &lu_getconfig; # first set up the ACL data. @add_users = &lu_parse_acl_line($config{user_acl}, 0); @del_users = &lu_parse_acl_line($config{user_acl}, 1); @add_groups = &lu_parse_acl_line($config{group_acl}, 0); @del_groups = &lu_parse_acl_line($config{group_acl}, 1); $ldap = Net::LDAP->new($config{ldap_host}, port => $config{ldap_port}, version => $config{ldap_version}); $ldap->bind( dn => $config{bind_dn}, password => $config{bind_pass}) or die("Could not bind to $config{ldap_host}: $!"); &lu_debug("Using base=$config{base_dn}, filter=$config{user_filter}, scope=$config{scope}", 1); $userdir = $ldap->search( base => $config{base_dn}, filter => $config{user_filter}, scope => $config{scope}) or die("Could not search with base $config{base_dn} and \ filter $config{user_filter}"); $userdir->code && die($userdir->error); $grpdir = $ldap->search( base => $config{base_dn}, filter => $config{grp_filter}, scope => $config{scope}) or die("Could not search with base $config{base_dn} and \ filter $config{grp_filter}"); $grpdir->code && die($grpdir->error); if ($config{debug} >= 1) { print ${userdir}->count, " user entries found\n"; print ${grpdir}->count, " group entries found\n"; } # Grab information from the master head files. @uexist = &lu_mfile($config{master_pwd_head}, $config{master_pwd_re}); @gexist = &lu_mfile($config{master_grp_head}, $config{master_grp_re}); my %user; my $users = 0; my $groupname; my @members; my $groups = 0; my $groupnum; # prep the gid and gmembers hashes. foreach my $dir_res ($grpdir->entries) { $groupname = $dir_res->get_value("cn"); $gid{$groupname} = $dir_res->get_value("gidNumber"); $gmembers{$groupname} = &lu_arrtostr($dir_res->get_value("memberUid")); } foreach my $dir_res($userdir->entries) { $user{uid} = $dir_res->get_value("uid"); $user{uidNumber} = $dir_res->get_value("uidNumber"); $user{gidNumber} = $dir_res->get_value("gidNumber"); $user{gecos} = $dir_res->get_value("gecos") or $user{gecos} = $dir_res->get_value("cn") or $user{gecos} = $config{noname}; $user{homedirectory} = $dir_res->get_value("homedirectory"); $user{loginshell} = $dir_res->get_value("loginshell") or $user{loginshell} = $config{nologin}; $user{cn} = $dir_res->get_value("cn") or $user{cn} = $config{noname}; $user{passwd} = "*"; if ($config{get_pass}) { my $temppass = $dir_res->get_value("userPassword"); $temppass =~ s/{crypt}//g; $user{passwd} = $temppass or $user{passwd} = "*"; } &lu_debug("uid=$user{uid} uidNumber=$user{uidNumber}", 2); &lu_debug("gid=$user{gidNumber}", 3); &lu_debug("gecos=$user{gecos}", 3); &lu_debug("cn=$user{cn}", 3); &lu_debug("homedirectory=$user{homedirectory}", 3); &lu_debug("loginshell=$user{loginshell}", 3); $u[$users] = &lu_add_user(\@uexist, %user); $users++; } &lu_debug("Looking at groups...", 1); foreach my $dir_res($grpdir->entries) { $groupname = $dir_res->get_value("cn"); $groupnum = $gid{$groupname}; &lu_debug("Reading group $groupname...", 3); @members = $dir_res->get_value("memberUid"); # debug numbers should be the same for this control statement. if ($config{debug} >= 3) { foreach my $xxx(@members) { &lu_debug("memberUid: $xxx", 3); } } $g[$groups] = &lu_add_group($groupname, $groupnum, \@gexist, \@members); $groups++; } unbind $ldap || die("Could not unbind from $config{ldap_host}: $!"); # Do the thing. &lu_update_files(\@g, \@u); &lu_move_files_in_place(); exit 0; } sub lu_update_files { my ($gref, $uref) = @_; my $i; &lu_debug("Prepping changes to the machine's authorization files..", 1); system("/bin/cp $config{master_pwd_head} $config{master_pwd}.tmp"); open (MASTER_PWD, ">> $config{master_pwd}.tmp") or &lu_errx("Can't open $config{master_pwd}.tmp!", 1); for ($i = 0;$i <= $#{$uref};$i++) { if (${$uref}[$i] !~ m/^0$/) { &lu_debug("Ok to add ${$uref}[$i] to $config{master_pwd}...", 2); print MASTER_PWD ${$uref}[$i], "\n"; } } close(MASTER_PWD); system("/bin/cp $config{master_grp_head} $config{master_grp}.tmp"); open (MASTER_GRP, ">> $config{master_grp}.tmp") or &lu_errx("Can't open $config{master_grp}.tmp!", 1); for ($i = 0;$i <= $#{$gref};$i++) { if (${$gref}[$i] !~ m/^0$/) { &lu_debug("Ok to add ${$gref}[$i] to $config{master_grp}", 2); print MASTER_GRP ${$gref}[$i], "\n"; } } close(MASTER_GRP); } sub lu_move_files_in_place { &lu_debug("Committing changes to the machine's authorization files..", 1); if (-f "$config{master_pwd}.tmp") { system("/bin/mv $config{master_pwd}.tmp $config{master_pwd}"); system("/usr/sbin/pwd_mkdb $config{pwd_mkdb_opts} $config{master_pwd}"); } else { &lu_debug("Can't pwd_mkdb $config{master_pwd}!", 1); } if (-f "$config{master_grp}.tmp") { system("/bin/mv $config{master_grp}.tmp $config{master_grp}"); } else { &lu_debug("Can't move $config{master_grp} in place!", 1); } } sub lu_getconfig { $config{debug} = $opts{d} if $opts{d}; $config{conf_file} = $opts{f} if $opts{f}; $config{ldap_host} = $opts{h} if $opts{h}; $config{bind_dn} = $opts{D} if $opts{D}; if ($config{conf_file}) { # Now we read in data from specified file. my $var; my $value; if (!open(CONF, "$config{conf_file}")) { &lu_errx("Can't open $config{conf_file}: $!", 1); } else { while () { chomp; chomp; next if m/^#/; # skip comments next if m/^$/; # skip blank lines ($var, $value) = split(/=/,$_,2); &lu_debug("Obtained var=$var and value=$value", 2); # check to make sure we didn't get something bogus. foreach my $varname (@varnames) { if ($var =~ m/$varname/) { &lu_debug("Setting \$config{$var} to $value", 2); # we found a valid varname, assign. $config{$var} = $value; } } } close(CONF); } } } sub lu_debug { my ($str, $debuglvl) = @_; if ($config{debug} >= $debuglvl) { print "$str\n"; } } sub lu_add_user { my ($uref, %uhash) = @_; my $tmp; my $trash; my $str; my $i; $uhash{user_conflict} = 0; # Default string to return. $str = "0"; for ($i = 0;$i <= $#{$uref};$i++) { &lu_debug("Comparing $uhash{uid} to ${$uref}[$i]...", 3); if ($uhash{uid} =~ m/^${$uref}[$i]$/) { $uhash{user_conflict} = 1; $i = $#{$uref}; } } # Attempt to ACL any users. Policy is to check if any users match # a "delete" first, then an "add". Also, if there are _only_ added # users, then only _those_ users will be added. Group delete, then add # operations take priority over these. if ($#del_users == -1 && $#add_users >= 0) { $uhash{user_conflict} = 1; } foreach my $i (@del_users) { &lu_debug("Checking user $uhash{uid} against $i\n", 1); if ($i eq $uhash{uid}) { &lu_debug(" --> Conflict.\n", 1); $uhash{user_conflict} = 1; } } foreach my $i (@add_users) { &lu_debug("Checking this user against $i\n", 1); if ($i eq $uhash{uid}) { &lu_debug(" --> Matches $i\n", 1); $uhash{user_conflict} = 0; } } if ($#add_groups >= 0 || $#del_groups >= 0) { # If only add_groups were specified, assume we remove the rest. if ($#del_groups == -1) { &lu_debug("Only adding groups matched.\n", 1); $uhash{user_conflict} = 1; } foreach my $i (@del_groups) { # first check to see if the user's gid matches any of the groups. &lu_debug("Checking GID $uhash{gidNumber} against $gid{$i}", 1); if ($uhash{gidNumber} eq $gid{$i}) { &lu_debug(" --> Conflict.\n", 1); $uhash{user_conflict} = 1; } # next check to see if the user is listed in any of the groups if ($gmembers{$i} =~ m/^.*$uhash{uid}.*$/) { &lu_debug(" --> Adding.\n", 1); $uhash{user_conflict} = 1; } } foreach my $i (@add_groups) { &lu_debug("Checking GID $uhash{gidNumber} against $gid{$i}", 1); if ($uhash{gidNumber} eq $gid{$i}) { &lu_debug(" --> Matches.\n", 1); $uhash{user_conflict} = 0; } &lu_debug("Checking group $uhash{uid} against members of $i.\n", 1); if ($gmembers{$i} =~ m/^.*$uhash{uid}.*$/) { &lu_debug(" --> Matched.\n", 1); $uhash{user_conflict} = 0; } } } if ($uhash{uidNumber} > 65535) { &lu_debug("Can't add user $uhash{uid}, uid #$uhash{uidNumber} > 65535!", 1); $uhash{user_conflict} = 1; } if ($uhash{user_conflict} == 0) { &lu_debug("Adding user $uhash{uid}...", 3); $str = "$uhash{uid}:$uhash{passwd}:$uhash{uidNumber}:$uhash{gidNumber}::0:0:"; $str = "${str}$uhash{gecos}:$uhash{homedirectory}:$uhash{loginshell}"; } return ($str); } sub lu_add_group { my ($groupname, $groupnum, $gref, $memref) = @_; my $i; my $add; my $str; # Default string to return. $str = "0"; &lu_debug("Looking at group $groupname...", 3); $add = 1; for ($i = 0;$i <= $#{$gref}; $i++) { if ($groupname =~ m/${$gref}[$i]/) { $add = 0; } } # ACL's for groups. No add by default if only adds specified if ($#add_groups >= 0 && $#del_groups == -1) { $add = 0; } foreach my $i (@del_groups) { if ($i eq $groupname) { $add = 0; } } foreach my $i (@add_groups) { if ($i eq $groupname) { $add = 1; } } if ($add == 1) { &lu_debug("Adding group $groupname...", 3); $str = "$groupname:*:$groupnum:"; for ($i = 0;$i <= $#{$memref}; $i++) { $str = "${str}${$memref}[$i]"; &lu_debug("Adding memberUid: ${$memref}[$i]", 3); if ($i != ($#{$memref})) { $str = "${str},"; } } } return($str); } sub lu_errx { my ($str, $exval) = @_; print "$str\n"; exit $exval; } sub lu_mfile { my ($file, $re) = @_; my @exist; my $tmp; my $trash; if (!open(MASTER_FILE, "< $file")) { &lu_errx("Can't open file $file $!", 1); } else { while () { &lu_debug("Found $_ in $file", 3); next if !($_ =~ m//); # skip blank lines ($tmp, $trash) = split(/:/, $_, 2); &lu_debug("Pushing $tmp in $file", 2); push(@exist, $tmp); } close(MASTER_FILE); } return(@exist); } sub lu_parse_acl_line { my ($line, $op) = @_; my @lst; my $j; my @args = split(/,/, $line); foreach my $i (@args) { if ($op == 1) { if ($i =~ m/^-.*$/) { $i =~ s/^-//g; push(@lst, $i); } } elsif ($op == 0) { if ($i =~ m/^\+.*$/) { $i =~ s/^\+//g; push(@lst, $i); } } } return (@lst); } sub lu_arrtostr { my @arr = @_; my $str = ""; foreach my $i (@arr) { if ("$str" ne "") { $str = "$str,"; } $str = "$str$i"; } return $str; }