1#!/usr/local/bin/perl -wT
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5#
6# This Source Code Form is "Incompatible With Secondary Licenses", as
7# defined by the Mozilla Public License, v. 2.0.
8
9use strict;
10
11use lib qw(. lib);
12
13use Net::LDAP;
14use Bugzilla;
15use Bugzilla::User;
16
17my $cgi = Bugzilla->cgi;
18my $dbh = Bugzilla->dbh;
19
20my $readonly = 0;
21my $nodisable = 0;
22my $noupdate = 0;
23my $nocreate = 0;
24my $quiet    = 0;
25
26###
27# Do some preparations
28###
29foreach my $arg (@ARGV)
30{
31   if($arg eq '-r') {
32      $readonly = 1;
33   }
34   elsif($arg eq '-d') {
35      $nodisable = 1;
36   }
37   elsif($arg eq '-u') {
38      $noupdate = 1;
39   }
40   elsif($arg eq '-c') {
41      $nocreate = 1;
42   }
43   elsif($arg eq '-q') {
44      $quiet = 1;
45   }
46   else {
47         print "LDAP Sync Script\n";
48         print "Syncronizes the users table from the LDAP server with the Bugzilla users.\n";
49         print "Takes mail-attribute from preferences and description from 'cn' or,\n";
50         print "if not available, from the uid-attribute.\n\n";
51         print "usage:\n syncLDAP.pl [options]\n\n";
52         print "options:\n";
53         print " -r Readonly, do not make changes to Bugzilla tables\n";
54         print " -d No disable, don't disable login by users who are not in LDAP\n";
55         print " -u No update, don't update users, which have different description in LDAP\n";
56         print " -c No create, don't create users, which are in LDAP but not in Bugzilla\n";
57         print " -q Quiet mode, give less output\n";
58         print "\n";
59         exit;
60   }
61}
62
63my %ldap_users;
64
65###
66# Get current bugzilla users
67###
68my %bugzilla_users = %{ $dbh->selectall_hashref(
69    'SELECT login_name AS new_login_name, realname, disabledtext ' .
70    'FROM profiles', 'new_login_name') };
71
72foreach my $login_name (keys %bugzilla_users) {
73    # remove whitespaces
74    $bugzilla_users{$login_name}{'realname'} =~ s/^\s+|\s+$//g;
75}
76
77###
78# Get current LDAP users
79###
80my $LDAPserver = Bugzilla->params->{"LDAPserver"};
81if ($LDAPserver eq "") {
82   print "No LDAP server defined in bugzilla preferences.\n";
83   exit;
84}
85
86my $LDAPconn;
87if($LDAPserver =~ /:\/\//) {
88    # if the "LDAPserver" parameter is in uri scheme
89    $LDAPconn = Net::LDAP->new($LDAPserver, version => 3);
90} else {
91    my $LDAPport = "389";  # default LDAP port
92    if($LDAPserver =~ /:/) {
93        ($LDAPserver, $LDAPport) = split(":",$LDAPserver);
94    }
95    $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
96}
97
98if(!$LDAPconn) {
99   print "Connecting to LDAP server failed. Check LDAPserver setting.\n";
100   exit;
101}
102my $mesg;
103if (Bugzilla->params->{"LDAPbinddn"}) {
104    my ($LDAPbinddn,$LDAPbindpass) = split(":",Bugzilla->params->{"LDAPbinddn"});
105    $mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
106}
107else {
108    $mesg = $LDAPconn->bind();
109}
110if($mesg->code) {
111   print "Binding to LDAP server failed: " . $mesg->error . "\nCheck LDAPbinddn setting.\n";
112   exit;
113}
114
115# We've got our anonymous bind;  let's look up the users.
116$mesg = $LDAPconn->search( base   => Bugzilla->params->{"LDAPBaseDN"},
117                           scope  => "sub",
118                           filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} . "=*)" . Bugzilla->params->{"LDAPfilter"} . ')',
119                         );
120
121
122if(! $mesg->count) {
123   print "LDAP lookup failure. Check LDAPBaseDN setting.\n";
124   exit;
125}
126
127my %val = %{ $mesg->as_struct };
128
129while( my ($key, $value) = each(%val) ) {
130
131   my @login_name = @{ $value->{Bugzilla->params->{"LDAPmailattribute"}} };
132   my @realname  = @{ $value->{"cn"} };
133
134   # no mail entered? go to next
135   if(! @login_name) {
136      print "$key has no valid mail address\n";
137      next;
138   }
139
140   # no cn entered? use uid instead
141   if(! @realname) {
142       @realname = @{ $value->{Bugzilla->params->{"LDAPuidattribute"}} };
143   }
144
145   my $login = shift @login_name;
146   my $real = shift @realname;
147   $ldap_users{$login} = { realname => $real };
148}
149
150print "\n" unless $quiet;
151
152###
153# Sort the users into disable/update/create-Lists and display everything
154###
155my %disable_users;
156my %update_users;
157my %create_users;
158
159print "Bugzilla-Users: \n" unless $quiet;
160while( my ($key, $value) = each(%bugzilla_users) ) {
161  print " " . $key . " '" . $value->{'realname'} . "' " . $value->{'disabledtext'} ."\n" unless $quiet==1;
162  if(!exists $ldap_users{$key}){
163     if($value->{'disabledtext'} eq '') {
164       $disable_users{$key} = $value;
165     }
166  }
167}
168
169print "\nLDAP-Users: \n" unless $quiet;
170while( my ($key, $value) = each(%ldap_users) ) {
171  print " " . $key . " '" . $value->{'realname'} . "'\n" unless $quiet==1;
172  if(!defined $bugzilla_users{$key}){
173    $create_users{$key} = $value;
174  }
175  else {
176    my $bugzilla_user_value = $bugzilla_users{$key};
177    if($bugzilla_user_value->{'realname'} ne $value->{'realname'}) {
178      $update_users{$key} = $value;
179    }
180  }
181}
182
183print "\nDetecting email changes: \n" unless $quiet;
184while( my ($create_key, $create_value) = each(%create_users) ) {
185  while( my ($disable_key, $disable_value) = each(%disable_users) ) {
186    if($create_value->{'realname'} eq $disable_value->{'realname'}) {
187       print " " . $disable_key . " => " . $create_key ."'\n" unless $quiet==1;
188       $update_users{$disable_key} = { realname => $create_value->{'realname'},
189                                       new_login_name => $create_key };
190       delete $create_users{$create_key};
191       delete $disable_users{$disable_key};
192    }
193  }
194}
195
196if($quiet == 0) {
197   print "\nUsers to disable login for: \n";
198   while( my ($key, $value) = each(%disable_users) ) {
199     print " " . $key . " '" . $value->{'realname'} . "'\n";
200   }
201
202   print "\nUsers to update: \n";
203   while( my ($key, $value) = each(%update_users) ) {
204     print " " . $key . " '" . $value->{'realname'} . "' ";
205     if(defined $value->{'new_login_name'}) {
206       print "has changed email to " . $value->{'new_login_name'};
207     }
208     print "\n";
209   }
210
211   print "\nUsers to create: \n";
212   while( my ($key, $value) = each(%create_users) ) {
213     print " " . $key . " '" . $value->{'realname'} . "'\n";
214   }
215
216   print "\n\n";
217}
218
219
220###
221# now do the DB-Update
222###
223if($readonly == 0) {
224   print "Performing DB update:\nPhase 1: disabling login for users not in LDAP... " unless $quiet;
225
226   my $sth_disable = $dbh->prepare(
227       'UPDATE profiles
228           SET disabledtext = ?
229         WHERE ' . $dbh->sql_istrcmp('login_name', '?'));
230
231   if($nodisable == 0) {
232      while( my ($key, $value) = each(%disable_users) ) {
233        $sth_disable->execute('auto-disabled by ldap sync', $key);
234      }
235      print "done!\n" unless $quiet;
236   }
237   else {
238      print "disabled!\n" unless $quiet;
239   }
240
241   print "Phase 2: updating existing users... " unless $quiet;
242
243   if($noupdate == 0) {
244      while( my ($key, $value) = each(%update_users) ) {
245        my $user = Bugzilla::User->check($key);
246        if(defined $value->{'new_login_name'}) {
247          $user->set_login($value->{'new_login_name'});
248        } else {
249          $user->set_name($value->{'realname'});
250        }
251        $user->update();
252      }
253      print "done!\n" unless $quiet;
254   }
255   else {
256      print "disabled!\n" unless $quiet;
257   }
258
259   print "Phase 3: creating new users... " unless $quiet;
260   if($nocreate == 0) {
261      while( my ($key, $value) = each(%create_users) ) {
262        Bugzilla::User->create({
263            login_name => $key,
264            realname   => $value->{'realname'},
265            cryptpassword   => '*'});
266      }
267      print "done!\n" unless $quiet;
268   }
269   else {
270      print "disabled!\n" unless $quiet;
271   }
272}
273else
274{
275   print "No changes to DB because readonly mode\n" unless $quiet;
276}
277