]> git.uio.no Git - usit-rt.git/blame - local/bin/rt-sync-ldap
Upgrade to 4.2.8
[usit-rt.git] / local / bin / rt-sync-ldap
CommitLineData
92d29b48
MKG
1#!/usr/bin/perl
2#
3# Author: Petter Reinholdtsen
4# Date: 2005-08-08
5# License: GPL
6#
7# Update RT user information with information from an LDAP database.
8
9# Location of RT's libs and scripts
10# Remember to change to correct path on current RT instance
3ffc5f4f
MKG
11use lib '/www/data/rt/perl/share/perl5';
12use lib '/www/data/rt/perl/lib/perl5';
13use lib '/www/data/rt/perl/lib64/perl5';
92d29b48
MKG
14use lib ("/www/var/rt/local/lib", "/www/var/rt/lib");
15
16use strict;
17use warnings;
18
19use Getopt::Std;
20
21use Net::LDAP qw(LDAP_SUCCESS LDAP_SERVER_DOWN LDAP_OPERATIONS_ERROR);
22use Net::LDAP::Util qw (ldap_error_name);
23use RT::Interface::CLI qw(CleanEnv);
24use RT::User;
25
26my %opts;
27
28getopts("dn", \%opts);
29
30CleanEnv();
31RT::LoadConfig();
32RT::Init();
33
34my $LdapServer = "ldap.uio.no";
35
36my %system_users = ('root' => "Superuser",
37 'rt-user' => "Internal user",
38 'Nobody' => "No user at all",
39 'RT_System' => "Internal user");
40
41# Only the entries present here will be updated, no matter what the
42# LDAP database contain.
43#
44# Not including Name to make sure users don't suddenly change user
45# name because their mail address happen to point to a different user.
46my @updatevalues =
47 qw(
48 EmailAddress
49 RealName
50 WorkPhone
51 Address1
52 Address2
53 );
54
55# How to map from LDAP to RT values for the threes being queried for
56# information.
57my @LdapUserMap =
58 (
59 {
60 base => 'cn=targets,cn=mail,dc=uio,dc=no',
61 search => 'target',
62 filter => '(&(objectClass=mailAddr)(targetType=user))',
63 scope => 'one',
64 map => {'target' => 'Name',
65 'defaultMailAddress' => 'EmailAddress'}
66 },
67 {
68 base => 'cn=people,dc=uio,dc=no',
69 search => 'uid',
70 filter => '(objectClass=person)',
71 scope => '',
72 map => {'telephoneNumber' => 'WorkPhone',
73 'street' => 'Address1',
74 'postalAddress' => 'Address2'}
75 },
76 {
77 base => 'cn=users,cn=system,dc=uio,dc=no',
78 search => 'uid',
79 filter => '(objectclass=posixAccount)',
80 scope => '',
81 map => {'cn' => 'RealName',
82 'telephoneNumber' => 'WorkPhone'},
83 }
84 );
85
86my $ldaphandle = LdapConnect();
87
88# Loop over all RT users
89my $users = new RT::Users($RT::SystemUser);
90while (my $user = $users->Next) {
91 my $username = $user->Name;
92 next if ($system_users{$username});
93 next if ($username =~ m/@/); # Ignore external users
94
95 my %current;
96 for my $key (@updatevalues) {
97 $current{$key} = $user->$key;
98 }
99
100 my ($found, @info) =
101 LookupExternalUserInfoByName($user->Name);
102
103 # XXX LDAP lookup of RealName is not implemented in
104 # LookupExternalUserInfo() [pere 2005-08-08]
105 if (!$found) {
106 my $msg = "User '$username' is missing in LDAP";
107 $RT::Logger->info("$msg"); print "$msg\n" if $opts{'d'};
108 # XXX Deleted user? Should it be converted to an external
109 # user, by replacing the username with the mail address?
110 # Not sure. Ignore it for now. [pere 2005-08-08]
111 next;
112 }
113
114 # Convert return value to perl hash when we know it is a hash, to
115 # avoid warning about odd number of elements when user is missing
116 # in LDAP.
117 my %userinfo = @info;
118
119 for my $key (sort keys %userinfo) {
120 #print " $key - '$userinfo{$key}' cmp '$current{$key}'\n" if $opts{'d'};
121 if (exists $current{$key} && $current{$key} ne $userinfo{$key}) {
122 my $dryrun = $opts{'n'} ? " (dryrun)" : "";
123
124 my $msg = "Updating $key for user '$username' from LDAP: ".
125 "'$current{$key}' to '$userinfo{$key}'$dryrun";
126 $RT::Logger->info("$msg"); print "$msg\n" if $opts{'d'};
127
128 my $method = "Set$key";
129 my ($retval, $retmsg) = $user->$method($userinfo{$key})
130 unless $opts{'n'};
131 }
132 }
133}
134
135LdapDisconnect($ldaphandle);
136
137exit 0;
138
139=head2 LookupExternalUserInfoByName
140
141Look up a username in several subtrees of LDAP, and pass the
142information found back to the caller.
143
144=cut
145
146sub LookupExternalUserInfoByName {
147 my ($name) = @_;
148
149 # Wash the name to avoid surprises when searching for it.
150 if ($name =~ m/^([a-z0-9_.-]+)$/) {
151 $name = $1;
152 } else {
153 my $msg = "LookupExternalUserInfoByName: ".
154 "Illegal username '$name' rejected.";
155 $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
156 return (0, undef);
157 }
158
159 my %userinfo;
160 my $found = 0;
161 for my $ldaptree (@LdapUserMap) {
162 retry:
163 my @attrs = keys %{$ldaptree->{'map'}};
164 my $filter = "(&($ldaptree->{search}=$name)$ldaptree->{filter})";
165 my $mesg = $ldaphandle->search(base => $ldaptree->{base},
166 filter => $filter,
167 attrs => [@attrs]);
168
169 # Handle timeouts
170 if (($mesg->code == LDAP_SERVER_DOWN) or
171 ($mesg->code == LDAP_OPERATIONS_ERROR)) {
172 my $msg = "LookupExternalUserInfoByName: ".
173 "Connection time out. Reconnecting";
174 $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
175 $ldaphandle = LdapConnect();
176 goto retry if ($ldaphandle);
177 $msg = "LookupExternalUserInfoByName: ".
178 "Reconnect failed. Giving up!";
179 $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
180 last;
181 }
182
183 if ($mesg->code == LDAP_SUCCESS) {
184 if (1 == $mesg->count) {
185 # Got working result. Use it.
186 while( my $entry = $mesg->shift_entry) {
187 foreach my $attr (keys %{$ldaptree->{'map'}}) {
188 foreach my $value ($entry->get_value($attr)) {
189 # Let Perl know this is UTF-8
190 $value = Encode::decode_utf8( $value );
191 $userinfo{$ldaptree->{'map'}{$attr}} = $value;
192 }
193 }
194 }
195 $found = 1;
196 } elsif (1 < $mesg->count) {
197 my $msg = "LookupExternalUserInfoByName: ".
198 "Searching for $filter returned $mesg->count entries. ".
199 "It should return only one.";
200 $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
201 } # else count == 0 -> no hit, nothing to report
202 } else {
203 my $msg = "LookupExternalUserInfoByName: ".
204 "Could not search for $filter: " .
205 "retval=" . $mesg->code . " " . ldap_error_name($mesg->code);
206 $RT::Logger->debug($msg); print "$msg\n" if $opts{'d'};
207 }
208 }
209 return ($found, %userinfo)
210}
211
212
213sub LdapConnect {
214
215 my $mesg;
216 my $ldap = Net::LDAP->new($LdapServer,
217 version => 3);
218
219 unless ($ldap) {
220 $RT::Logger->critical("rt-sync-ldap: Cannot connect to",
221 "LDAP server ", $LdapServer);
222 return undef;
223 }
224 $mesg = $ldap->bind;
225 if ($mesg->code != LDAP_SUCCESS) {
226 $RT::Logger->critical("rt-sync-ldap: Cannot bind to LDAP: ",
227 "retval=", $mesg->code, " ",
228 ldap_error_name($mesg->code));
229 return undef;
230 }
231 return $ldap;
232}
233
234sub LdapDisconnect {
235 my $ldap = shift;
236 my $mesg = $ldap->unbind();
237 if ($mesg->code != LDAP_SUCCESS) {
238 $RT::Logger->critical("LdapDisconnect: unbind failed: ",
239 "retval=", $mesg->code, " ",
240 ldap_error_name($mesg->code));
241 }
242}