--- /dev/null
+#!/usr/bin/perl
+#
+# Author: Petter Reinholdtsen
+# Date: 2005-08-08
+# License: GPL
+#
+# Update RT user information with information from an LDAP database.
+
+# Location of RT's libs and scripts
+# Remember to change to correct path on current RT instance
+use lib '/www/data/rt/rt-perl/current-perl10/share/perl5';
+use lib '/www/data/rt/rt-perl/current-perl10/lib/perl5';
+use lib '/www/data/rt/rt-perl/current-perl10/lib64/perl5';
+use lib ("/www/var/rt/local/lib", "/www/var/rt/lib");
+
+use strict;
+use warnings;
+
+use Getopt::Std;
+
+use Net::LDAP qw(LDAP_SUCCESS LDAP_SERVER_DOWN LDAP_OPERATIONS_ERROR);
+use Net::LDAP::Util qw (ldap_error_name);
+use RT::Interface::CLI qw(CleanEnv);
+use RT::User;
+
+my %opts;
+
+getopts("dn", \%opts);
+
+CleanEnv();
+RT::LoadConfig();
+RT::Init();
+
+my $LdapServer = "ldap.uio.no";
+
+my %system_users = ('root' => "Superuser",
+ 'rt-user' => "Internal user",
+ 'Nobody' => "No user at all",
+ 'RT_System' => "Internal user");
+
+# Only the entries present here will be updated, no matter what the
+# LDAP database contain.
+#
+# Not including Name to make sure users don't suddenly change user
+# name because their mail address happen to point to a different user.
+my @updatevalues =
+ qw(
+ EmailAddress
+ RealName
+ WorkPhone
+ Address1
+ Address2
+ );
+
+# How to map from LDAP to RT values for the threes being queried for
+# information.
+my @LdapUserMap =
+ (
+ {
+ base => 'cn=targets,cn=mail,dc=uio,dc=no',
+ search => 'target',
+ filter => '(&(objectClass=mailAddr)(targetType=user))',
+ scope => 'one',
+ map => {'target' => 'Name',
+ 'defaultMailAddress' => 'EmailAddress'}
+ },
+ {
+ base => 'cn=people,dc=uio,dc=no',
+ search => 'uid',
+ filter => '(objectClass=person)',
+ scope => '',
+ map => {'telephoneNumber' => 'WorkPhone',
+ 'street' => 'Address1',
+ 'postalAddress' => 'Address2'}
+ },
+ {
+ base => 'cn=users,cn=system,dc=uio,dc=no',
+ search => 'uid',
+ filter => '(objectclass=posixAccount)',
+ scope => '',
+ map => {'cn' => 'RealName',
+ 'telephoneNumber' => 'WorkPhone'},
+ }
+ );
+
+my $ldaphandle = LdapConnect();
+
+# Loop over all RT users
+my $users = new RT::Users($RT::SystemUser);
+while (my $user = $users->Next) {
+ my $username = $user->Name;
+ next if ($system_users{$username});
+ next if ($username =~ m/@/); # Ignore external users
+
+ my %current;
+ for my $key (@updatevalues) {
+ $current{$key} = $user->$key;
+ }
+
+ my ($found, @info) =
+ LookupExternalUserInfoByName($user->Name);
+
+ # XXX LDAP lookup of RealName is not implemented in
+ # LookupExternalUserInfo() [pere 2005-08-08]
+ if (!$found) {
+ my $msg = "User '$username' is missing in LDAP";
+ $RT::Logger->info("$msg"); print "$msg\n" if $opts{'d'};
+ # XXX Deleted user? Should it be converted to an external
+ # user, by replacing the username with the mail address?
+ # Not sure. Ignore it for now. [pere 2005-08-08]
+ next;
+ }
+
+ # Convert return value to perl hash when we know it is a hash, to
+ # avoid warning about odd number of elements when user is missing
+ # in LDAP.
+ my %userinfo = @info;
+
+ for my $key (sort keys %userinfo) {
+ #print " $key - '$userinfo{$key}' cmp '$current{$key}'\n" if $opts{'d'};
+ if (exists $current{$key} && $current{$key} ne $userinfo{$key}) {
+ my $dryrun = $opts{'n'} ? " (dryrun)" : "";
+
+ my $msg = "Updating $key for user '$username' from LDAP: ".
+ "'$current{$key}' to '$userinfo{$key}'$dryrun";
+ $RT::Logger->info("$msg"); print "$msg\n" if $opts{'d'};
+
+ my $method = "Set$key";
+ my ($retval, $retmsg) = $user->$method($userinfo{$key})
+ unless $opts{'n'};
+ }
+ }
+}
+
+LdapDisconnect($ldaphandle);
+
+exit 0;
+
+=head2 LookupExternalUserInfoByName
+
+Look up a username in several subtrees of LDAP, and pass the
+information found back to the caller.
+
+=cut
+
+sub LookupExternalUserInfoByName {
+ my ($name) = @_;
+
+ # Wash the name to avoid surprises when searching for it.
+ if ($name =~ m/^([a-z0-9_.-]+)$/) {
+ $name = $1;
+ } else {
+ my $msg = "LookupExternalUserInfoByName: ".
+ "Illegal username '$name' rejected.";
+ $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
+ return (0, undef);
+ }
+
+ my %userinfo;
+ my $found = 0;
+ for my $ldaptree (@LdapUserMap) {
+ retry:
+ my @attrs = keys %{$ldaptree->{'map'}};
+ my $filter = "(&($ldaptree->{search}=$name)$ldaptree->{filter})";
+ my $mesg = $ldaphandle->search(base => $ldaptree->{base},
+ filter => $filter,
+ attrs => [@attrs]);
+
+ # Handle timeouts
+ if (($mesg->code == LDAP_SERVER_DOWN) or
+ ($mesg->code == LDAP_OPERATIONS_ERROR)) {
+ my $msg = "LookupExternalUserInfoByName: ".
+ "Connection time out. Reconnecting";
+ $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
+ $ldaphandle = LdapConnect();
+ goto retry if ($ldaphandle);
+ $msg = "LookupExternalUserInfoByName: ".
+ "Reconnect failed. Giving up!";
+ $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
+ last;
+ }
+
+ if ($mesg->code == LDAP_SUCCESS) {
+ if (1 == $mesg->count) {
+ # Got working result. Use it.
+ while( my $entry = $mesg->shift_entry) {
+ foreach my $attr (keys %{$ldaptree->{'map'}}) {
+ foreach my $value ($entry->get_value($attr)) {
+ # Let Perl know this is UTF-8
+ $value = Encode::decode_utf8( $value );
+ $userinfo{$ldaptree->{'map'}{$attr}} = $value;
+ }
+ }
+ }
+ $found = 1;
+ } elsif (1 < $mesg->count) {
+ my $msg = "LookupExternalUserInfoByName: ".
+ "Searching for $filter returned $mesg->count entries. ".
+ "It should return only one.";
+ $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
+ } # else count == 0 -> no hit, nothing to report
+ } else {
+ my $msg = "LookupExternalUserInfoByName: ".
+ "Could not search for $filter: " .
+ "retval=" . $mesg->code . " " . ldap_error_name($mesg->code);
+ $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
+ }
+ }
+ return ($found, %userinfo)
+}
+
+
+sub LdapConnect {
+
+ my $mesg;
+ my $ldap = Net::LDAP->new($LdapServer,
+ version => 3);
+
+ unless ($ldap) {
+ $RT::Logger->critical("rt-sync-ldap: Cannot connect to",
+ "LDAP server ", $LdapServer);
+ return undef;
+ }
+ $mesg = $ldap->bind;
+ if ($mesg->code != LDAP_SUCCESS) {
+ $RT::Logger->critical("rt-sync-ldap: Cannot bind to LDAP: ",
+ "retval=", $mesg->code, " ",
+ ldap_error_name($mesg->code));
+ return undef;
+ }
+ return $ldap;
+}
+
+sub LdapDisconnect {
+ my $ldap = shift;
+ my $mesg = $ldap->unbind();
+ if ($mesg->code != LDAP_SUCCESS) {
+ $RT::Logger->critical("LdapDisconnect: unbind failed: ",
+ "retval=", $mesg->code, " ",
+ ldap_error_name($mesg->code));
+ }
+}