]>
Commit | Line | Data |
---|---|---|
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 |
11 | use lib '/www/data/rt/perl/share/perl5'; |
12 | use lib '/www/data/rt/perl/lib/perl5'; | |
13 | use lib '/www/data/rt/perl/lib64/perl5'; | |
92d29b48 MKG |
14 | use lib ("/www/var/rt/local/lib", "/www/var/rt/lib"); |
15 | ||
16 | use strict; | |
17 | use warnings; | |
18 | ||
19 | use Getopt::Std; | |
20 | ||
21 | use Net::LDAP qw(LDAP_SUCCESS LDAP_SERVER_DOWN LDAP_OPERATIONS_ERROR); | |
22 | use Net::LDAP::Util qw (ldap_error_name); | |
23 | use RT::Interface::CLI qw(CleanEnv); | |
24 | use RT::User; | |
25 | ||
26 | my %opts; | |
27 | ||
28 | getopts("dn", \%opts); | |
29 | ||
30 | CleanEnv(); | |
31 | RT::LoadConfig(); | |
32 | RT::Init(); | |
33 | ||
34 | my $LdapServer = "ldap.uio.no"; | |
35 | ||
36 | my %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. | |
46 | my @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. | |
57 | my @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 | ||
86 | my $ldaphandle = LdapConnect(); | |
87 | ||
88 | # Loop over all RT users | |
89 | my $users = new RT::Users($RT::SystemUser); | |
90 | while (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 | ||
135 | LdapDisconnect($ldaphandle); | |
136 | ||
137 | exit 0; | |
138 | ||
139 | =head2 LookupExternalUserInfoByName | |
140 | ||
141 | Look up a username in several subtrees of LDAP, and pass the | |
142 | information found back to the caller. | |
143 | ||
144 | =cut | |
145 | ||
146 | sub 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 | ||
213 | sub 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 | ||
234 | sub 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 | } |