package LATMOS::Accounts::Bases::Ldap::User; use 5.010000; use strict; use warnings; use base qw(LATMOS::Accounts::Bases::Ldap::objects); use Net::LDAP; use Net::LDAPS; use Net::LDAP::Entry; use Net::LDAP::Control::Paged; use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); use Net::LDAP::Util qw( escape_filter_value ); use LATMOS::Accounts::Log; use LATMOS::Accounts::Bases::Ldap::Group; use LATMOS::Accounts::Utils; use Crypt::SmbHash; our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0]; =head1 NAME LATMOS::Ldap - Perl extension for blah blah blah =head1 SYNOPSIS use LATMOS::Ldap; blah blah blah =head1 DESCRIPTION Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head1 FUNCTIONS =cut sub _class_filter { '(&(ObjectClass=posixAccount))' } sub _key_attr { 'cn' } sub _my_ldap_classes { qw( top inetOrgPerson organizationalPerson posixAccount shadowAccount sambaSamAccount ) } sub _computeSSID { my ($value) = @_; $value * 2 + 1000 } sub _get_attr_schema { my ($class, $base) = @_; $class->SUPER::_get_attr_schema($base, { displayName => { }, givenName => { }, homePhone => { }, homePostalAddress => { }, initials => { }, mail => { }, sn => { }, mobile => { }, o => { }, uid => { mandatory => 1 }, manager => { delayed => 1, can_values => sub { $base->list_objects('user') }, }, facsimileTelephoneNumber => { }, uidNumber => { uniq => 1, post => sub { my ($self, $value) = @_; $self->object->set_fields(sambaSID => $self->object->base->sambaSID(_computeSSID($value))); }, }, gidNumber => { reference => 'group', mandatory => 1, can_values => sub { map { $base->get_object('group', $_)->get_attributes('gidNumber') } $base->list_objects('group') }, display => sub { my ($self, $val) = @_; my ($gr) = $self->base->search_objects('group', "gidNumber=$val") or return; return $gr; }, }, homeDirectory => { }, loginShell => { }, gecos => { }, description => { }, shadowLastChange => { }, shadowMin => { }, shadowMax => { }, shadowWarning => { }, shadowInactive => { }, shadowExpire => { }, shadowFlag => { }, street => { }, postOfficeBox => { }, postalCode => { }, postalAddress => { }, streetAddress => { }, physicalDeliveryOfficeName => { }, ou => { }, st => { }, l => { }, telephoneNumber => { }, memberOf => { delayed => 1, multiple => 1, reference => 'group', }, cn => { ro => 1, }, dn => { ro => 1, }, objectClass => { ro => 1, }, pwdAccountLockedTime => {}, pwdPolicySubentry => {}, pwdChangedTime => { ro => 1 }, labeledURI => {}, userPassword => { readable => 0, }, sambaLMPassword => { readable => 0, }, sambaNTPassword => { readable => 0, }, sambaSID => { ro => 1 }, } ); } sub _update_class { my ($self) = @_; $self->SUPER::_update_class( sambaSID => $self->base->sambaSID( _computeSSID($self->_get_attributes('uidNumber')) ) ); } sub _create { my ($class, $base, $id, %data) = @_; my $entry = Net::LDAP::Entry->new(); $entry->dn(join(',', sprintf('%s=%s', $class->_dn_attribute($base), escape_filter_value($id)), $base->object_base_dn($class->type), ) ); $entry->replace(objectClass => [ $class->_my_ldap_classes ],); $data{cn} ||= $data{sn} || $id; # cn is mandatory $data{sn} ||= $id; # sn is mandatory $data{uid} ||= $id; # uid is mandatory $data{sambaSID} = $base->sambaSID(_computeSSID($data{uidNumber})); $data{homeDirectory} ||= '/dev/null'; # homeDirectory is mandatory $data{$class->_key_attribute($base)} = $id; foreach (keys %data) { $class->_populate_entry($entry, $_, $data{$_}, $base); } my $msg = $base->ldap->add($entry); if ($msg->code) { $base->log(LA_ERR, "Cannot create user: %s", $msg->error); return 0; } return 1; } sub _rename { my ($class, $base, $uid, $newuid) = @_; $class->SUPER::_rename($base, $uid, $newuid) or return; } sub get_field { my ($self, $field) = @_; $field eq 'streetAddress' and $field = 'street'; $field eq 'memberOf' and do { my @res; $self->base->_unlimited_search( base => $self->base->object_base_dn('group'), filter => sprintf( '(&(objectClass=posixGroup)(memberUID=%s))', escape_filter_value($self->id), ), callback => sub { my ($mesg, $entry) = @_; ref $entry eq 'Net::LDAP::Entry' or return; push(@res, $entry->get_value( LATMOS::Accounts::Bases::Ldap::Group->_key_attribute($self->base), )); 1; }, ); return [ sort(@res) ]; }; $field eq 'manager' and do { my $dn = $self->SUPER::get_field($field) or return; my $manager = $self->base->_get_object_from_dn($dn) or return; return $manager->get_value(__PACKAGE__->_key_attribute($self->base)); }; $self->SUPER::get_field($field); } sub _populate_entry { my ($self, $entry, $f, $val, $base) = @_; $base ||= $self->base; for ($f) { /^sn$/ and $val ||= $entry->get_value($self->_key_attribute); /^memberOf$/ and do { my %users; $users{$_}{e} = 1 foreach (ref $self ? $self->get_attributes('memberOf') : ()); $val = [ $val ] unless(ref $val); $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []}); foreach (keys %users) { $users{$_}{e} && $users{$_}{n} and next; my $group = $base->get_object('group', $_) or next; if ($users{$_}{e}) { $group->{entry}->delete(memberUID => $entry->get_value( LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base), ) ); } elsif ($users{$_}{n}) { $group->{entry}->add(memberUID => $entry->get_value( LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base), ) ); } # else {} # can't happen my $mesg = $group->{entry}->update($base->ldap); if ($mesg->code) { $base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error); return; } } return 1; }; /^manager$/ && $val and do { my $user = $base->get_object('user', $val) or do { $val = undef; next; }; $val = $user->get_field('dn'); next; }; /^gecos$/ and do { $val = to_ascii($val); }; /^homeDirectory$/ and $val ||= '/dev/null'; } $self->SUPER::_populate_entry($entry, $f, $val, $base); } sub _set_password { my ($self, $clear_pass) = @_; my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.'); my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8)); # openldap use prefix to identify encryption passwd # {CRYPT} is system dependant, eg use crypt from system # As we run openldap on UNIX, this should not be a problem # as we use perl crypt() which does the same # This code will have to be changed if we use openldap on other UNIX my $md5 = '{CRYPT}' . crypt($clear_pass, '$1$' . $salt); my ($lm, $nt) = ntlmgen $clear_pass; my $res = $self->set_fields( userPassword => $md5, sambaLMPassword => $lm, sambaNTPassword => $nt, ); $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id) if($res); return $res; } =head2 _InjectCryptPasswd($cryptpasswd) Inject a password encrypted using standard UNIX method. Works only for unix authentification method inside LDAP =cut sub _InjectCryptPasswd { my ($self, $cryptpasswd) = @_; my $res = $self->set_fields( userPassword => '{CRYPT}' . $cryptpasswd, ); if ($res) { $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id); return 1; } else { $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id); return 0; } } 1; __END__ =head1 SEE ALSO =head1 AUTHOR Olivier Thauvin, Eolivier.thauvin@aerov.jussieu.frE =head1 COPYRIGHT AND LICENSE Copyright (C) 2008 CNRS SA/CETP/LATMOS This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available. =cut