Ldapmigrate

From LinuxWiki

Jump to: navigation, search

This script, "ldapmigrate", will read current unix account information and store it within appropriate objects in an LDAP database.

Contents

Source

#!/usr/bin/perl
#
# Version: 0.84
#
# Script to populate LDAP server to support
# nss_ldap and pam_ldap clients.
#
# Name: GuruLabs.com LDAP migrate script
# Version: 0.83
# Copyright 2003 Dax Kelson <dax@gurulabs.com> 2003
#
# Modified by Brian White <bcwhite@pobox.com> to use "Groups"
# instead of "Group" for consistency with the LDAP Account
# Manager and to match the plurality of the other unit names.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the latest version of the GNU
# General Public License as published by the Free Software
# Foundation.
#
# 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 General Public License for more details.
#
# http://www.gnu.org/licenses/gpl.txt


use strict;
use Getopt::Long;
use Net::LDAP;
Getopt::Long::Configure ("bundling");

# All that is really needed
my @containers = ('People', 'Groups', 'Mounts');
# There is an outside chance you may need these
my @extcontainers = ('Protocols', 'Services' ,'Netgroup');

my $container; my $dc; my $ldap; my $result; my $dbcount;
my %Users; my %ShadowFile; my %Groups;

# Defaults for command line options
my $debug = 0; my $quiet = 0; my $basedn = '';
my $password = ''; my $binddn = '';
my $host = "127.0.0.1"; my $secure = 0;
my $database = ''; my $file = ''; my $help = 0;
my $prepdb = 0; my $extconts = 0; my $minuid = 500;
my $maxuid = 65500; my $extschema = 0; my $usage = 0;

GetOptions ('v' => \$debug, 'quiet' => \$quiet,
	'pass|w=s' => \$password, 'basedn|b=s' => \$basedn,
	'host|h|=s' => \$host, 'binddn|D=s' => \$binddn,
	'help|?' => \$help, 'file|f=s' => \$file,
	'Z' => \$secure, '<>' => \&process,
	'prepdb|P' => \$prepdb, 'extconts' => \$extconts,
	'extschema' => \$extschema, 'usage' => \$usage,
	'minuid=i' => \$minuid, 'maxuid=i' => \$maxuid,
	'mingid=i' => \$minuid, 'maxgid=i' => \$maxuid);

if ($help == 0 && (($database eq "" && $prepdb == 0) || $usage || ($database ne "" && $file eq ""))) {
print <<USAGE;
Usage:
ldapmigrate [-Zv] -b baseDN [--extconts]
-D bindDN [-w bind_password] [-h host] --prepdb

ldapmigrate [-Zv] -b baseDN -D bindDN
[-w bind_password] [-h host] -f /path/file database

Currently supported databases: passwd group

If password isn't supplied on command line, you
will be prompted for it.

Try `ldapmigrate --help` for more documentation
USAGE
exit 0;
}

if ($help) {
print <<HELP;

The --prepdb option prepares a blank LDAP directory by
adding containers: People, Group, and Mounts

The -Z option enables startTLS to secure the
connection to the LDAP server. Requires that startTLS
has been enabled on the server.

Mounts is for autofs and importing of /etc/auto* files
and is currently unsupported.

If you add the option, --extconts, you also get:
Protocols, Services, and Netgroup. Small chance you'll
need those however.

When importing users, add the option --extschema to
the command line to create more general User objects
so that you can use the same objects with address books
and the like.

By default accounts and groups with UIDs and GIDs
less than 500 or greater than 65500 are ignored. This
can be changed with the --minuid/--mingid and
--maxuid/--maxgid options.

Example Usage:

ldapmigrate -h 192.168.31.14 -b "dc=example,dc=com" \
-D "cn=Manager,dc=example,dc=com" -w secret --prepdb

ldapmigrate -h 192.168.31.14 -b "dc=example,dc=com" \
-D "cn=Manager,dc=example,dc=com" -w secret \
-f /etc/passwd passwd

ldapmigrate -h 192.168.31.14 -b "dc=example,dc=com" \
-D "cn=Manager,dc=example,dc=com" -w secret \
-f /etc/group group

HELP
exit 0;
}

if ($password eq '') {
	print "Password: ";
	system("/bin/stty","-echo");
	chomp($password = <STDIN>);
	system("/bin/stty","echo");
}

if ($prepdb) {
	$ldap = Net::LDAP->new("$host", version => 3);
	$ldap->start_tls("$host") if ($secure);
	$ldap->bind ( $binddn, password => $password);

	($dc) = $basedn =~ /^dc=(.*?)($|,)/;

	$result = $ldap->add ( "$basedn",
                      			attr => [
					'dc'  => "$dc",
                                	'objectclass' => ['top', 'domain' ],
                              	]
				);
	$result->code && warn "failed to add entry $basedn: ", $result->error;
	&addcontainers($ldap, $basedn, @containers);
	if ($extconts) {
		&addcontainers($ldap, $basedn, @extcontainers);
	}
}

if ($database eq 'passwd') {
	print "Found db=passwd\n" if ($debug);
	# Populate the %ShadowFile hash
	&ReadShadowFile;
	# Populate the %Users hash
	&ReadPassdFile;
	# Insert into LDAP
	&AddPeople;
} elsif ($database eq 'group') {
	print "Found db=group\n" if ($debug);
	# Populate the %Groups hash
	&ReadGroupFile;
	# Insert into LDAP
	&AddGroups;
} elsif ($database eq "") {
	# --prepdb with no db specified
} else {
	print "Database: $database not supported yet\n";
	exit 1;
}

sub addcontainers {
	my ($ldap, $basedn, @containers) = @_;
	my $conainter;
	my $result;
	foreach $container (@containers) {
		$result = $ldap->add ( "ou=$container,$basedn",
				attr => [ 'ou' => "$container",
				'objectclass' => ['top', 'organizationalUnit' ],
				]
			);
		$result->code && warn "failed to add entry $container: ", $result->error;
	}
}

sub AddPeople {
	my $ldap; my $result; my $user;

	$ldap = Net::LDAP->new("$host", version => 3);
	$ldap->start_tls if ($secure);
	$ldap->bind ( $binddn, password => $password);
	foreach $user (sort keys %Users) {
		print "About to import user: $user\n" if ($debug);
		$result = $ldap->add (
				dn => "uid=$user,ou=People,$basedn",
				attr => [ %{$Users{$user}} ],
				);
		$result->code && warn "failed to add entry $user: ", $result->error;
	}
}

sub process {
	($database) = @_;
	if ($dbcount == 1) {
		die "ERROR: Only one database can be specified. Run $0 --help\n";
	}
	$dbcount = 1;
}

sub ReadShadowFile {
	my $user;
	open(SHADOW, "/etc/shadow") || warn "WARNING: Unable to open /etc/shadow\n";
	while(<SHADOW>) {
		next if /^[#\+]/;
		chop;
		($user) = split(/:/);
		$ShadowFile{$user} = $_;
	}
	close(SHADOW);
}

sub ReadPassdFile {
	my $pwd; my $lastchg; my $min; my $max;
	my $warn; my $inactive; my $expire; my $flag;
	open(PASSWD, "/etc/passwd") || die "FATAL: Unable to open /etc/passwd\n";
	while (<PASSWD>) {
		next if /^[#\+]/;
		my($user, $pwd, $uid, $gid, $gecos, $homedir, $shell) = split(/:/);
		chomp($shell);
		if ($uid >= $minuid && $uid <= $maxuid) {
			print "found passwd uid: $uid\n" if ($debug);
			$Users{$user}{'uid'} = $user;
			$Users{$user}{'cn'} = $user;
			$Users{$user}{'uidNumber'} = $uid;
			$Users{$user}{'gidNumber'} = $gid;
			$Users{$user}{'gecos'} = $gecos if ($gecos);
			$Users{$user}{'homeDirectory'} = $homedir if ($homedir);
			$Users{$user}{'loginShell'} = $shell if ($shell);
			$Users{$user}{'objectClass'} = ['posixAccount', 'top'];

			# See if there is a shadow file entry
			if ( (undef,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag) = split(/:/, $ShadowFile{$user})) {
				print "found shadow uid: $uid\n" if ($debug);
				push(@{$Users{$user}{'objectClass'}}, 'shadowAccount');
				$Users{$user}{'userPassword'} = "{crypt}$pwd" if ($pwd);
				$Users{$user}{'shadowLastChange'} = "$lastchg" if ($lastchg);
				$Users{$user}{'shadowMin'} = "$min" if ($min);
				$Users{$user}{'shadowMax'} = "$max" if ($max);
				$Users{$user}{'shadowWarning'} = "$warn" if ($warn);
				$Users{$user}{'shadowInactive'} = "$inactive" if ($inactive);
				$Users{$user}{'shadowExpire'} = "$expire" if ($expire);
				$Users{$user}{'shadowFlag'} = "$flag" if ($flag);
			} else {
				$Users{$user}{'userPassword'} = "{crypt}$pwd";
			}
		if ($extschema) {
			# If you use extschema, the person objectclass must have
			# an attribute 'sn'. Try using last name from the gecos field

			($Users{$user}{'sn'}) = $gecos =~ /.* (.*)$/ if ($gecos);

			push(@{$Users{$user}{'objectClass'}},'person');
			push(@{$Users{$user}{'objectClass'}},'organizationalPerson');
			push(@{$Users{$user}{'objectClass'}},'inetOrgPerson');
		} else {
			push(@{$Users{$user}{'objectClass'}}, 'account');
		}
		}
		undef $user; undef $pwd; undef $uid; undef $gid; undef $gecos;
		undef $homedir; undef $shell; undef $lastchg; undef $min; undef $max;
		undef $warn; undef $inactive; undef $expire; undef $flag;
	}
	close(PASSWD);
}

sub ReadGroupFile {
	my $user; my @members;
	my $group; my $pwd; my $gid; my $users;
	open(GROUP, "/etc/group") || die "FATAL: Unable to open /etc/group\n";
	while (<GROUP>) {
		next if /^[#\+]/;
		($group, $pwd, $gid, $users) = split(/:/);
		chomp($users);
		@members = split(/,/, $users);
		if ($gid >= $minuid && $gid <= $maxuid) {
			$Groups{$group}{'cn'} = $group;
			if ($pwd ne 'x') {
				$Groups{$group}{'UserPassword'} = "{crypt}$pwd";
			}
			$Groups{$group}{'gidNumber'} = $gid;
			$Groups{$group}{'objectClass'} = ['posixGroup', 'top'];
			foreach $user (@members) {
				push(@{$Groups{$group}{'memberUid'}},"$user");
			}
		}
		undef $group; undef $pwd; undef $gid; undef $users;
	}
	close(GROUP);
}

sub AddGroups {
	my $ldap; my $result; my $group;

	$ldap = Net::LDAP->new("$host", version => 3);
	$ldap->start_tls if ($secure);
	$ldap->bind ( $binddn, password => $password);
	foreach $group (sort keys %Groups) {
		print "About to import group: $group\n" if ($debug);
		$result = $ldap->add (
				dn => "cn=$group,ou=Groups,$basedn",
				attr => [ %{$Groups{$group}} ],
				);
		$result->code && warn "failed to add entry $group: ", $result->error;
	}
}

Copyright

This script has been released under the GNU General Public License.

History

2003-??-?? v0.83

Originally found at http://www.gurulabs.com/goodies/downloads.php . Copyright 2003 Dax Kelson <dax@gurulabs.com>

2006-05-20 v0.84

Modified by Brian White <bcwhite@precidia.com> to use "Groups" instead of "Group" for unix group storage within the LDAP directory.