centralized authentication and user information

introduction
this is not a "howto" for creating centralized authentication, it's rather a "what i know"

what i want to do is have all my usernames and passwords in one location and access any PC in my network with it, and have a global address book available to all users through their mail client.
after reading this you should be able to run an openLDAP server populate it with users information and authenticate other linux PCs to it and provide the user information in thunderbird addressbook (isn't that cool)
TODO


let's do it
Open LDAP Packages

configuration files

now after you install the packages you start by editing slapd.conf


#
# See slapd.conf(5) for details on configuration options.
# This file should NOT be world readable.
#
include		/etc/openldap/schema/core.schema
include		/etc/openldap/schema/cosine.schema
include		/etc/openldap/schema/inetorgperson.schema
include		/etc/openldap/schema/nis.schema

pidfile		/var/run/slapd.pid
argsfile	/var/run/slapd.args

## ACL ##

access to * by * read

#######################################################################
# ldbm and/or bdb database definitions
#######################################################################

database	bdb
suffix		"dc=lab,dc=local"
rootdn		"cn=admin,dc=lab,dc=local"
rootpw		{crypt}

defaultaccess   read

directory	/var/lib/ldap

# Indices to maintain for this database
index objectClass                       eq,pres
index ou,cn,mail,surname,givenname      eq,pres,sub
#index uidNumber,gidNumber,loginShell    eq,pres
index uid,memberUid                     eq,pres,sub
#index nisMapName,nisMapEntry            eq,pres,sub

the first declare which schemas will your entries obey (i don't know much about them yet)
then defining args and pid file, then access controls (the line there lets anybody have read access)
defines the backend
defines the top of your ldap structure
admin user for the ldap server
the password for the admin this can be an encrypted password or a plain text password depending on what you insert in the file if you use {crypt} it's encrypted if you just put it in plain text it's plain text it's better to use crypt so that the password won't be readable by anyone who can read the file you can use either #ldappasswd command or you can use the perl script crypt.pl at the end of this document
the rest is not really important to get started so i won't get into that now
now that we have configured the server, let's start it and populate it with user entries.
now some concepts (don't rely on me for concepts), these are my 2cents notes on understanding this issue

example of an entry for a user (see how it has DN object to identify it and the a set of attributes for details)


dn: uid=john,ou=people,dc=example,dc=com
cn: John Doe
uid: john
uidNumber: 1001
gidNumber: 100
homeDirectory: /home/john
loginShell: /bin/bash
objectClass: top
objectClass: posixAccount

now looking at this example we can see that it is an entry for a posixaccount (unix user account) that the pam_ldap would look for to get user information.
Now how do we populate such data in ldap, simple there is a file format called LDIF (ldap data interchange format) that does it, you open an empty text file, you write in your entries and then ldapadd -W -D "cn=admin,dc=lab,dc=local" -f file.ldif while the server is running. this command will create the database entries which will be located in /var/lib/ldap/ (as defined in the slapd.conf) and if you delete these files you can start from scratch and populate new data.
now the most important thing to do thetrick is to figure out what to put in that ldif file (that took a lot of time and reading, lucky for you i did my homework and you can bypass that time, i hope) here is my sample file


dn: dc=lab, dc=local
objectClass: top
objectclass: organization
objectclass: dcObject
o: Opencraft labs
dc: lab

dn: ou=users,dc=lab, dc=local
ou: users
objectClass: top
objectClass: organizationalUnit

dn: ou=groups,dc=lab, dc=local
ou: groups
objectClass: top
objectClass: organizationalUnit

dn: cn=engineering, ou=groups, dc=lab, dc=local
objectclass: top
objectclass: posixGroup
cn: engineering
gidnumber: 500
memberuid: foo

dn: cn=ramez hanna,ou=users,dc=lab, dc=local
objectClass: top
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: ramez
userpassword: {crypt}
uidnumber: 500
gidnumber: 500
gecos:ramez zoheir hanna
loginShell:/bin/bash
homeDirectory: /home/rhanna
shadowLastChange:10877
shadowMin: 0
shadowMax: 999999
shadowWarning: 7
shadowInactive: -1
shadowExpire: -1
shadowFlag: 0
cn: ramez hanna
givenname: ramez
sn: hanna       
mail: me@domain.com
title: CEO
StreetAddress: somewhere 
l: cairo
postalCode: huh
telephoneNumber: 54545454
homephone: 454545454
mobile: 555555555
facsimileTelephoneNumber: 555555

ok now one last tip, when you add a new user to the system you will first add the entry to ldap then create a group with the same name and then create the home folde /home/$username and make it owned by the user and it's group
now i wish you good luck if you chose to follow my steps and try it

references :

scripts:


srand (time());
my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))";
my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
my $plaintext = <STDIN>;
my $crypttext = "{CRYPT}".crypt ($plaintext, $salt);

#!/usr/bin/perl

use Net::LDAP;

# configuration section
# edit the next lines to meet your environment
# 
$server = "ldap.opencraft.local";
$suffix = "dc=opencraft,dc=local";
$rootdn = "cn=admin,$suffix"; #if not present the script will ask for it
$ou = "ou=People";
$gou = "ou=Group";
$homeDir = "/home/";
# end of configuration

$action = shift;
if ($action eq "useradd") {
	$username = shift;
	$options = shift;
	$groupname = $username;
	$_ = $options;
	if (/^-g/) {
		$groupname = $';
	}		
	ifexist($username,"user","exit");
	ifexist($groupname,"group","exit");
	adduser();
	addgroup($groupname,"auto");
}
elsif ($action eq "groupadd") {
	$group = shift;
	ifexist($group,"group","exit");
	$idNumber = generate_uid_num();
	addgroup($group,"user");
}
elsif ($action eq "passwd") {
	$username = shift;
	print "changing password for user $username";
	ifexist($username,"user","continue");
	`ldappasswd -x -v -S -w secret -Dcn=admin,dc=opencraft,dc=local uid=$username,ou=People,dc=opencraft,dc=local`;
}
elsif ($action eq "usermod") {
	$username = shift;
	$changes = shift;
	ifexist($username,"user","continue");
	usermod();
}
else {
	die "you have to specify an action";
}

################################ sub routines ##############################
#
# connect to ldap server
sub ldapconnect {
	$ldap = Net::LDAP->new( $server, version => 3 ) or die "new failed : $!";
	$ldap->bind( $rootdn,password => 'secret') or die "cannot bind :$!";
}
# disconnect from the server 
sub ldapdisconnect {
	$ldap->unbind;
}
# add a user 
sub adduser {
	open(TMP,">/tmp/ldapuser.tmp") || die "cannot cache data: $1";
	open(TEMPLATE,"< adduser.ldif") || die "cannot read template file: $!";
	while (<TEMPLATE>) {
           if (!/^#/) {
		@field = split(/:/,$_);
		if ($field[1,1] eq "") {
			if ($field[1,0] eq "dn") {
				$field[1,1] = "uid=$username,$ou,$suffix\n";
			}
			elsif ($field[1,0] eq "uid") {
				$field[1,1] = $username."\n";
			}
			elsif ($field[1,0] eq "userPassword") {
				print "enter value of $field[1,0]-$field[1,2]";
				$crypted = generate_pass();
				$field[1,1] = $crypted."\n";
                        }
			elsif ($field[1,0] eq "uidNumber") {
				$idNumber = generate_uid_num();
				$field[1,1] = $idNumber."\n";
                        }
			elsif ($field[1,0] eq "gidNumber") {
                                $field[1,1] = $idNumber."\n";
                        }
			elsif ($field[1,0] eq "homeDirectory") {
				$field[1,1] = $homeDir.$username."\n";
                        }
			elsif ($field[1,0] eq "cn") {
				print "enter value of $field[1,0]-$field[1,2]";
                                my $name = <STDIN>;
                                $name =~ s/\n//;
				$field[1,1] = $name."\n";
                        }
			else {
				print "enter value of $field[1,0]-$field[1,2]";
				$field[1,1] = <STDIN>;
			}
		}
		print TMP "$field[1,0]:$field[1,1]";
	   }
	}
	close (TMP);
	print "adding user $username to LDAP . . .";
	`ldapadd -x -w secret -D"$rootdn" -f /tmp/ldapuser.tmp`;
	print "user added\n";
	print "creating home folder . . .";
	system ("cp -Rfa /etc/skel/ $homeDir$username & chown -R $username:$username $homeDir$username");
}
# encrypt the password using {crypt}
sub generate_pass {
        srand (time());
	my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))";
	my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
	my $plaintext = <STDIN>;
	my $crypttext = "{CRYPT}".crypt ($plaintext, $salt);
	return $crypttext;
}
# get the latest uid/gid number to get a new uid for the user or the group
sub generate_uid_num {
	ldapconnect();
	$searchbase = $suffix;
	my $filter = "!(uidNumber=65534)";
	my $attrs = "";
	my $results = $ldap->search(base=>$searchbase,filter=>$filter,attrs=>$attrs);
	my $count = $results->count;
	my $entry;
	my $list;
	for (my $i=0; $i<$count; $i++) {
	        $entry = $results->entry($i);
		if (($entry->get_value('uidNumber')>499  and $entry->get_value('uidNumber')<65534)or($entry->get_value('gidNumber')>499 and $entry->get_value('gidNumber')<65534)) {
                $list = $list.",".$entry->get_value('uidNumber').",".$entry->get_value('gidNumber');
        	}
	}
	ldapdisconnect();
	my @uids = split(/,/,$list);
	@uids = sort { $a <=> $b } @uids;
	my $count = @uids;
	my $last = $uids[$count-1];
	my $newuid = $last+1;
	return $newuid;
}
# add a group
sub addgroup {
	my $name = @_[0];
	my $mode = @_[1];
	open(TMP2,">/tmp/ldapgroup.tmp") || die "cannot cache data: $1";
	open(TEMPLATE2,"< addgroup.ldif") || die "cannot read template file: $!";
	while (<TEMPLATE2>) {
           if (!/^#/) {
		@field = split(/:/,$_);
		if ($field[1,1] eq "") {
			if ($field[1,0] eq "dn") {
				$field[1,1] = "cn=$name,$gou,$suffix\n";
				print TMP2 "$field[1,0]:$field[1,1]";
			}
			elsif ($field[1,0] eq "gidNumber") {
                                $field[1,1] = $idNumber."\n";
				print TMP2 "$field[1,0]:$field[1,1]";
                        }
			elsif ($field[1,0] eq "cn") {
				$field[1,1] = $name."\n";
				print TMP2 "$field[1,0]:$field[1,1]";
                        }
			elsif ($field[1,0] eq "memberUid") {
				if ($mode eq "user") {
					print "enter value of $field[1,0]-$field[1,2]";
					my $input = <STDIN>;
					chomp ($input);
					my @members = split(/,/,$input);
					ldapconnect();
					foreach $member (@members) {	
					        $searchbase = $ou.",".$suffix;
					        my $filter = "uid=$member";
					        my $attrs = "uidNumber";
					        my $results = $ldap->search(base=>$searchbase,filter=>$filter,attrs=>$attrs);
						my $entry = $results->entry(0);
						my $memberUid = $entry->get_value('uidNumber');
						$field[1,1] ="$memberUid\n";
						print TMP2 "$field[1,0]:$field[1,1]";
					}
				ldapdisconnect();
				}
				elsif ($mode eq "auto") {
					$field[1,1] = $idNumber;
					print TMP2 "$field[1,0]:$field[1,1]";
				}
			}
		}
		else { print TMP2 "$field[1,0]:$field[1,1]"; }
	   }
	}
	close (TMP);
	print "adding group $group to LDAP . . .";
	`ldapadd -x -w secret -D"$rootdn" -f /tmp/ldapgroup.tmp`;
	print "group added\n";
}
# modify the users attributes
sub usermod {
	my %ReplaceHash = ( keyword => "x", proxy => "x" );
        my $result = LDAPmodifyUsingHash ( $ldap, $dn, \%ReplaceHash );
        my $result = $ldap->modify ( $dn,replace => { %$whatToChange } );
        return $result;
}
# check if user/group exists
sub ifexist {
	ldapconnect();
        my ($attrib,$entity,$action) = @_;
	my $searchbas,$filter,$attrs;
	if ($entity eq "group") {
		$searchbase = $gou.",".$suffix;
		$filter = "cn=$attrib";
		$attrs = "gidNumber";
	}
	else {
		$searchbase = $ou.",".$suffix;
		$filter = "uid=$attrib";
		$attrs = "uidNumber";
	}
        my $results = $ldap->search(base=>$searchbase,filter=>$filter,attrs=>$attrs);
	my $count = $results->count;
	if ($count > 0) {
		if ($action eq "exit") {
			print STDOUT "dependency failure ( user/group exists\nin case you are adding new user you can use -g with useradd to assign another default group)\n";
			exit;
		}
	}
	ldapdisconnect();
}

#adduser.ldif the template for new users
#this is the template for creating users
#syntax:
#attr:value:comment
#only missing values will be prompted by the scrip
#note that even if you won't have comments you need to add the ":"
dn::identifier usually the full name or the username
objectClass:top
objectClass:person
objectClass:posixAccount
objectClass:shadowAccount
objectClass:organizationalPerson
objectClass:inetOrgPerson
uid::username
userPassword::password not less than 5 char
uidNumber::
gidNumber::
gecos::more information divided by commas
loginShell:/bin/bash
homeDirectory::home folder
shadowLastChange:10877
shadowMin:0
shadowMax:999999
shadowWarning:7
shadowInactive:-1
shadowExpire:-1
shadowFlag:0
cn::
givenname::surname
sn::full name
mail::user's email
title::job title
StreetAddress::home address
l::city
telephoneNumber::home phone number
mobile::mobile number