]> git.uio.no Git - usit-rt.git/blobdiff - local/bin/rt-sync-ldap
Ported and added rt-sync-ldap from 3.8
[usit-rt.git] / local / bin / rt-sync-ldap
diff --git a/local/bin/rt-sync-ldap b/local/bin/rt-sync-ldap
new file mode 100755 (executable)
index 0000000..6180b03
--- /dev/null
@@ -0,0 +1,242 @@
+#!/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));
+    }
+}