Download
#!/usr/bin/perl -w
# $Revision: 1.4 $
#
# Adds user to ldap server running on ldap://localhost:389
#
# prints LDIF if --ldif is passed
#
use strict;
$|++; # disable buffer (autoflush)
use Net::LDAP;
use Net::LDAP::Entry;
use URI;
use Getopt::Long;
Getopt::Long::Configure('bundling');
my $USAGE =
'adduser-ldap [--uid="username"] <--first="First"> <--last="Lastname"> [--password="passwd"] [--email="mail_id"] <--domain="domain.com"> [--organizational-unit|--ou="People"] [--posix] [--nt] [--uid-number] [--gid=number] [--home-path|--home] [-D|--bind] [-w|--bind-password="ldapsecret"] [-W|--bind-password-file="/etc/adduser-ldap.secret"] [--groups="group1,group2,..."] [--host="ldapserver"] [| ldapadd -x -D "cn=Directory Manager" -W]'
. "
Example: adduser-ldap --uid=ismith --first=ivan --last=smith --email='ismith\@example.org' --domain=example.org --posix --uid-number=1023 --ldif --home-path=/home/ismith --password=secret123 --ou=People --groups=mygroup |ldapadd -x -D 'cn=Directory Manager' -W
";
#my $pass_cmd = "slappasswd";
my $pass_scheme = "\{CRYPT\}"; # SSHA, SHA1, MD5, CRYPT
#my $search_cmd = "ldapsearch -x -Z localhost ";
my $min_uid = "2000";
my $max_uid = "65000";
my $HELP;
my $DEBUG;
my $POSIX;
my $NT;
my $OU;
my $LDIF;
my $LDAPADMINCN = ""; # -D or --bind
my $LDAPSERVER = "localhost";
my $LDAPPASSWORDFILE = "/etc/adduser-ldap.secret";
my $LDAPPASSWORD = "";
# all possible items we will use. note that some need
# other objectClasses added. order does not matter
my @records = qw(
carLicense
cn
departmentNumber
employeeNumber
employeeType
facsimileTelephoneNumber
gecos
gidNumber
givenName
homeDirectory
homePhone
initials
labeledURI
loginShell
mail
mobile
preferredLanguage
roomNumber
shadowExpire
shadowInactive
shadowMax
shadowMin
shadowWarning
sn
telephoneNumber
title
uid
uidNumber
userPassword
);
my %rec = ();
my $domain = ""; # yields dc=domain,dc=com
my @groups = (); # --groups
$rec{loginShell} = "/bin/false"; # TODO --shell
my $edomain = ""; # TODO --email-domain. defaults to $domain
# visible domain from the outside word
# TODO set this interactively:
$rec{'webhost'} = "www"; # name of web server, without the domain
GetOptions(
'debug' => \$DEBUG,
'posix' => \$POSIX,
'nt' => \$NT,
'f|first=s' => sub { $rec{givenName} = $_[1] },
'l|last=s' => sub { $rec{sn} = $_[1] },
'u|uid=s' => sub { $rec{uid} = $_[1] },
'e|m|email=s' => sub { $rec{mail} = $_[1] },
'd|domain=s' => \$domain,
'o|ou|organizational-unit=s' => \$OU,
'p|password=s' => sub { $rec{userPassword} = $_[1] },
'H|home|home-path=s' => sub { $rec{homeDirectory} = $_[1] },
'U|uid-number=i' => sub { $rec{uidNumber} = $_[1] },
'G|gid-number=i' => sub { $rec{gidNumber} = $_[1] },
'D|bind=s' => \$LDAPADMINCN,
'w|bind-password=s' => \$LDAPPASSWORD,
'W|bind-password-file=s' => \$LDAPPASSWORDFILE,
'host=s' => \$LDAPSERVER,
'groups=s' => sub {
my $str = $_[1];
foreach my $_g (split(/[[:blank:],]/, $str))
{
next if $_g =~ /^\s*$/;
push @groups, $_g;
}
},
'ldif' => \$LDIF,
'h|help' => \$HELP
);
if ($HELP) { print $USAGE; exit 0; }
print STDERR ($USAGE) and exit(1)
if (not exists $rec{givenName} || not exists $rec{sn} || not $domain);
# helpers
sub _debug
{
my $str = shift;
print STDERR "DEBUG: $str
" if ($DEBUG);
}
sub random_password
{
my $num = shift;
if ($num !~ /^[[:digit:]]+$/ or $num < 8)
{
$num = 8;
}
my $count = $num;
my @password_chars = ('.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z');
my $_password = undef;
for (1 .. $count)
{
$_password .= (@password_chars)[rand(@password_chars)];
}
return $_password;
}
sub hash_password
{
my $str = shift;
my $_scheme = shift;
my $hash = $str;
if ($_scheme =~ /crypt/i)
{
# generates an MD5 sum salted password with 8 random chars
$hash = crypt($str, "\$1\$" . random_password(8) . "\$");
}
return $hash;
}
sub _create_entry
{
my ($ldap, $whatToCreate) = @_; # $whatToCreate is Net::LDAP::Entry
my $result = $ldap->add($whatToCreate);
return $result;
}
# @param whatToModify a hashref like " userPassword => 'foo' "
sub _modify_entry
{
my ($ldap, $dn, $whatToModify) = @_;
my $result = $ldap->modify($dn, 'replace' => {%$whatToModify});
return $result;
}
sub _add_entry
{
my ($ldap, $dn, $whatToModify) = @_;
my $result = $ldap->modify($dn, 'add' => {%$whatToModify});
return $result;
}
sub _get_password
{
my $file = shift;
if (-r $file)
{
open(PW, "<$file") or die("Could not read file $file. " . $!);
undef $/; # slurp mode
my $content = <PW>;
$content =~ s/(
|
)//g;
return $content;
}
else
{
die("Could not read password file $file
");
}
}
# end helpers
# main()
# set some internal vars:
my @domain_parts = split(/\./, $domain);
$edomain = (not $edomain) ? $domain : $edomain;
# create UID and MID using scheme:
# (first letter of first name) + (last name)
$rec{uid} =
(exists $rec{uid} and $rec{uid} ne "")
? $rec{uid}
: lc(substr($rec{givenName}, 0, 1) . $rec{sn});
if (exists $rec{mail} and $rec{mail})
{
if ($rec{mail} !~ /@/)
{
$rec{mail} .= "\@" . $edomain;
}
}
else
{
$rec{mail} = lc(substr($rec{givenName}, 0, 1) . $rec{sn}) . "\@" . $edomain;
}
$rec{homeDirectory} =
(exists $rec{homeDirectory})
? $rec{homeDirectory}
: "/home/${rec{uid}}"; # --posix only
$rec{cn} = ucfirst($rec{givenName}) . " " . ucfirst($rec{sn});
$rec{initials} = substr($rec{givenName}, 0, 1) . substr($rec{sn}, 0, 1);
# --posix only
# TODO calculate uid number from previous LDAP user_list
$rec{uidNumber} =
(exists $rec{uidNumber} and $rec{uidNumber} >= 0)
? $rec{uidNumber}
: sprintf("%02d", rand(my @ary = ($min_uid .. $max_uid)));
$rec{gidNumber} =
(exists $rec{gidNumber} and $rec{gidNumber} >= 0)
? $rec{gidNumber}
: "100"; # TODO get a good GID number from LDAP
$rec{userPassword} =
(exists $rec{userPassword} and $rec{userPassword})
? hash_password($rec{userPassword}, $pass_scheme)
: hash_password(random_password(8), $pass_scheme);
$rec{labeledURI} =
"http://people.$edomain/${rec{givenName}}_${rec{sn}} User Home Page";
# TODO
$rec{title} = "Employee Title";
$rec{carLicense} = "000";
$rec{departmentNumber} = "0";
$rec{employeeNumber} = "0";
$rec{roomNumber} = "0";
$rec{employeeType} = "Full Time";
$rec{facsimileTelephoneNumber} = "000-000-0000";
$rec{homePhone} = "000-000-0000";
$rec{telephoneNumber} = "000-000-0000";
$rec{mobile} = "000-000-0000";
$rec{preferredLanguage}="en";
$rec{gecos} = "${rec{givenName}} ${rec{sn}},Home Address Here,${rec{telephoneNumber}},${rec{homePhone}},${rec{mobile}}";
# TODO sanity checks: mid, uid, uidNumber, gidNumber, ...
my $ou = ($OU) ? "ou=$OU, " : "";
my $domain_joined = "";
foreach (@domain_parts)
{
$domain_joined .= "dc=$_, ";
}
$domain_joined =~ s/, $//;
# this $ldif is used only if --ldif
my $ldif = "dn: uid=${rec{uid}}, $ou $domain_joined
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson";
$ldif .= "
objectClass: posixAccount
objectClass: shadowAccount
" if ($POSIX);
foreach my $_item (@records)
{
if (exists $rec{$_item})
{
$ldif .= "${_item}: " . $rec{$_item} . "
";
}
}
if ((@groups + 0))
{
foreach my $_group (@groups)
{
$ldif .= "
dn: cn=$_group,ou=Groups,$domain_joined
changetype: modify
add: uniqueMember
uniqueMember: uid=${rec{uid}}, $ou $domain_joined
"
}
}
# FIXME remove 1 when $ldap auth is done
if ($LDIF)
{
print STDOUT ($ldif, "
");
exit 0;
}
$LDAPADMINCN = ($LDAPADMINCN) ? $LDAPADMINCN : "cn=admin, $domain_joined";
_debug("connecting to $LDAPSERVER as $LDAPADMINCN");
my $ldap = Net::LDAP->new($LDAPSERVER);
my $mesg = $ldap->bind(
$LDAPADMINCN,
# --bind-password takes precedence
'password' => ($LDAPPASSWORD)
? $LDAPPASSWORD
: _get_password($LDAPPASSWORDFILE),
'version' => '3'
); # if bind() it binds anonymously
my $_dn = "uid=${rec{uid}},${ou}${domain_joined}";
my $entry = Net::LDAP::Entry->new($_dn);
my $CODE = 0; # assume we won't have errors
if ($POSIX)
{
$entry->add(
objectClass => [
"top", "person",
"organizationalPerson", "inetorgperson",
"shadowAccount", "posixAccount"
]
);
$rec{'shadowMin'} = "-1";
$rec{'shadowMax'} = "99999";
$rec{'shadowWarning'} = "7";
$rec{'shadowInactive'} = "-1";
$rec{'shadowExpire'} = "-1";
}
else
{
$entry->add(objectClass =>
["top", "person", "organizationalPerson", "inetorgperson"]);
}
foreach my $_item (@records)
{
$entry->add($_item => $rec{$_item});
}
my $entry_result = _create_entry($ldap, $entry);
if ($entry_result->code())
{
print STDERR
" Error while creating entry for uid ${rec{uid}} on \$LDAPSERVER
";
print STDERR ( ". Server message => code: "
. $entry_result->code()
. ". name: "
. $entry_result->error_name()
. ". text: "
. $entry_result->error_text());
$CODE = 1;
goto EXIT;
}
if (@groups + 0)
{
my $add_hash = undef;
foreach my $_group (@groups)
{
my $_dn = "cn=$_group,ou=Groups,$domain_joined";
my $add_hash =
['uniqueMember' => "uid=" . $rec{uid} . ", $ou $domain_joined"];
}
my $entry_result = _add_entry($ldap, $_dn, $add_hash);
if ($entry_result->code())
{
print(
" Error while adding uid ${rec{uid}} to group $_dn on $LDAPSERVER: "
. $entry_result->error_text());
goto EXIT;
}
}
EXIT:
$ldap->unbind(); # take down session
$ldap->disconnect();
exit $CODE;