Project

General

Profile

1
#!/usr/bin/perl -w
2
#
3
#  '$RCSfile$'
4
#  Copyright: 2001 Regents of the University of California 
5
#
6
#   '$Author: tao $'
7
#     '$Date: 2016-02-03 09:55:48 -0800 (Wed, 03 Feb 2016) $'
8
# '$Revision: 9514 $' 
9
# 
10
# This program is free software; you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation; either version 2 of the License, or
13
# (at your option) any later version.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# GNU General Public License for more details.
19
#
20
# You should have received a copy of the GNU General Public License
21
# along with this program; if not, write to the Free Software
22
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
#
24

    
25
#
26
# This is a web-based application for allowing users to register a new
27
# account for Metacat access.  We currently only support LDAP even
28
# though metacat could potentially support other types of directories.
29

    
30
use lib '../WEB-INF/lib';
31
use strict;             # turn on strict syntax checking
32
use Template;           # load the template-toolkit module
33
use CGI qw/:standard :html3/; # load the CGI module 
34
use Net::LDAP;          # load the LDAP net libraries
35
use Net::SMTP;          # load the SMTP net libraries
36
use Digest::SHA1;       # for creating the password hash
37
use MIME::Base64;       # for creating the password hash
38
use URI;                # for parsing URL syntax
39
use Config::Properties; # for parsing Java .properties files
40
use File::Basename;     # for path name parsing
41
use DateTime;			# for parsing dates
42
use DateTime::Duration; # for substracting
43
use Captcha::reCAPTCHA; # for protection against spams
44
use Cwd 'abs_path';
45
use Scalar::Util qw(looks_like_number);
46

    
47
# Global configuration paramters
48
# This entire block (including skin parsing) could be pushed out to a separate .pm file
49
my $cgiUrl = $ENV{'SCRIPT_FILENAME'};
50
my $workingDirectory = dirname($cgiUrl);
51
my $metacatProps = "${workingDirectory}/../WEB-INF/metacat.properties";
52
my $properties = new Config::Properties();
53
unless (open (METACAT_PROPERTIES, $metacatProps)) {
54
    print "Content-type: text/html\n\n";
55
    print "Unable to locate Metacat properties. Working directory is set as " . 
56
        $workingDirectory .", is this correct?";
57
    exit(0);
58
}
59

    
60
$properties->load(*METACAT_PROPERTIES);
61

    
62
# local directory configuration
63
my $skinsDir = "${workingDirectory}/../style/skins";
64
my $templatesDir = abs_path("${workingDirectory}/../style/common/templates");
65
my $tempDir = $properties->getProperty('application.tempDir');
66

    
67
# url configuration
68
my $server = $properties->splitToTree(qr/\./, 'server');
69
my $protocol = 'http://';
70
if ( $properties->getProperty('server.httpPort') eq '443' ) {
71
	$protocol = 'https://';
72
}
73
my $serverUrl = $protocol . $properties->getProperty('server.name');
74
if ($properties->getProperty('server.httpPort') ne '80') {
75
        $serverUrl = $serverUrl . ':' . $properties->getProperty('server.httpPort');
76
}
77
my $context = $properties->getProperty('application.context');
78
my $contextUrl = $serverUrl . '/' .  $context;
79

    
80
my $metacatUrl = $contextUrl . "/metacat";
81
my $cgiPrefix = "/" . $context . "/cgi-bin";
82
my $styleSkinsPath = $contextUrl . "/style/skins";
83
my $styleCommonPath = $contextUrl . "/style/common";
84
my $caCertFileProp = $properties->getProperty('ldap.server.ca.certificate');
85
my $ldapServerCACertFile;
86
if ($caCertFileProp eq "") {
87
   $ldapServerCACertFile = "/etc/ssl/certs/ca-certificates.crt";
88
   debug("Metacat doesn't specify the ca file, we use the default one " . $ldapServerCACertFile);
89
} else {
90
   $ldapServerCACertFile = $workingDirectory. "/../" . $properties->getProperty('ldap.server.ca.certificate');
91
   debug("Metacat does specify the ca file, we will use it - " . $ldapServerCACertFile);
92
}
93

    
94

    
95
#recaptcha key information
96
my $recaptchaPublicKey=$properties->getProperty('ldap.recaptcha.publickey');
97
my $recaptchaPrivateKey=$properties->getProperty('ldap.recaptcha.privatekey');
98

    
99
my @errorMessages;
100
my $error = 0;
101

    
102
my $emailVerification= 'emailverification';
103

    
104
 my $dn_store_next_uid=$properties->getProperty('ldap.nextuid.storing.dn');
105
 my $attribute_name_store_next_uid = $properties->getProperty('ldap.nextuid.storing.attributename');
106

    
107
# Import all of the HTML form fields as variables
108
import_names('FORM');
109

    
110
# Must have a config to use Metacat
111
my $skinName = "";
112
if ($FORM::cfg) {
113
    $skinName = $FORM::cfg;
114
} elsif ($ARGV[0]) {
115
    $skinName = $ARGV[0];
116
} else {
117
    debug("No configuration set.");
118
    print "Content-type: text/html\n\n";
119
    print 'LDAPweb Error: The registry requires a skin name to continue.';
120
    exit();
121
}
122

    
123
# Metacat isn't initialized, the registry will fail in strange ways.
124
if (!($metacatUrl)) {
125
    debug("No Metacat.");
126
    print "Content-type: text/html\n\n";
127
    'Registry Error: Metacat is not initialized! Make sure' .
128
        ' MetacatUrl is set correctly in ' .  $skinName . '.properties';
129
    exit();
130
}
131

    
132
my $skinProperties = new Config::Properties();
133
if (!($skinName)) {
134
    $error = "Application misconfigured.  Please contact the administrator.";
135
    push(@errorMessages, $error);
136
} else {
137
    my $skinProps = "$skinsDir/$skinName/$skinName.properties";
138
    unless (open (SKIN_PROPERTIES, $skinProps)) {
139
        print "Content-type: text/html\n\n";
140
        print "Unable to locate skin properties at $skinProps.  Is this path correct?";
141
        exit(0);
142
    }
143
    $skinProperties->load(*SKIN_PROPERTIES);
144
}
145

    
146
my $config = $skinProperties->splitToTree(qr/\./, 'registry.config');
147

    
148
# XXX HACK: this is a temporary fix to pull out the UCNRS password property from the
149
#           NRS skin instead of metacat.properties. The intent is to prevent editing
150
#           of our core properties file, which is manipulated purely through the web.
151
#           Once organizations are editable, this section should be removed as should
152
#           the properties within nrs/nrs.properties.
153
my $nrsProperties = new Config::Properties();
154
my $nrsProps = "$skinsDir/nrs/nrs.properties";
155
unless (open (NRS_PROPERTIES, $nrsProps)) {
156
    print "Content-type: text/html\n\n";
157
    print "Unable to locate skin properties at $nrsProps.  Is this path correct?";
158
    exit(0);
159
}
160
$nrsProperties->load(*NRS_PROPERTIES);
161

    
162
my $nrsConfig = $nrsProperties->splitToTree(qr/\./, 'registry.config');
163

    
164
# XXX END HACK
165

    
166

    
167
my $searchBase;
168
my $ldapUsername;
169
my $ldapPassword;
170
# TODO: when should we use surl instead? Is there a setting promoting one over the other?
171
# TODO: the default tree for accounts should be exposed somewhere, defaulting to unaffiliated
172
my $ldapurl = $properties->getProperty('auth.url');
173

    
174
# Java uses miliseconds, Perl expects whole seconds
175
my $timeout = $properties->getProperty('ldap.connectTimeLimit') / 1000;
176

    
177
# Get the CGI input variables
178
my $query = new CGI;
179
my $debug = 1;
180

    
181
#--------------------------------------------------------------------------80c->
182
# Set up the Template Toolkit to read html form templates
183

    
184
# templates hash, imported from ldap.templates tree in metacat.properties
185
my $templates = $properties->splitToTree(qr/\./, 'ldap.templates');
186
$$templates{'header'} = $skinProperties->getProperty("registry.templates.header");
187
$$templates{'footer'} = $skinProperties->getProperty("registry.templates.footer");
188

    
189
# set some configuration options for the template object
190
my $ttConfig = {
191
             INCLUDE_PATH => $templatesDir,
192
             INTERPOLATE  => 0,
193
             POST_CHOMP   => 1,
194
             DEBUG        => 1, 
195
             };
196

    
197
# create an instance of the template
198
my $template = Template->new($ttConfig) || handleGeneralServerFailure($Template::ERROR);
199

    
200
# custom LDAP properties hash
201
my $ldapCustom = $properties->splitToTree(qr/\./, 'ldap');
202

    
203
# This is a hash which has the keys of the organization's properties 'name', 'base', 'organization'.
204
my $orgProps = $properties->splitToTree(qr/\./, 'organization');
205

    
206
#This is a hash which has the keys of the ldap sub tree names of the organizations, such as 'NCEAS', 'LTER' and 'KU', and values are real name of the organization.
207
my $orgNames = $properties->splitToTree(qr/\./, 'organization.name');
208
# pull out properties available e.g. 'name', 'base'
209
my @orgData = keys(%$orgProps);
210

    
211
my @orgList; #An array has the names (i.e, sub tree names, such as 'NCEAS', 'LTER' and 'KU')  of the all organizations in the metacat.properties. 
212
while (my ($oKey, $oVal) = each(%$orgNames)) {
213
    push(@orgList, $oKey);
214
}
215

    
216
my $authBase = $properties->getProperty("auth.base");
217
my $ldapConfig;
218
foreach my $o (@orgList) {
219
    foreach my $d (@orgData) {
220
        $ldapConfig->{$o}{$d} = $properties->getProperty("organization.$d.$o");
221
    }
222

    
223
    # XXX hack, remove after 1.9
224
    if ($o eq 'UCNRS') {
225
        $ldapConfig->{'UCNRS'}{'base'} = $nrsConfig->{'base'};
226
        $ldapConfig->{'UCNRS'}{'user'} = $nrsConfig->{'username'};
227
        $ldapConfig->{'UCNRS'}{'password'} = $nrsConfig->{'password'};
228
    }
229

    
230
    # set default base
231
    if (!$ldapConfig->{$o}{'base'}) {
232
        $ldapConfig->{$o}{'base'} = $authBase;
233
    }
234

    
235
    # include filter information. By default, our filters are 'o=$name', e.g. 'o=NAPIER'
236
    # these can be overridden by specifying them in metacat.properties. Non-default configs
237
    # such as UCNRS must specify all LDAP properties.
238
    if ($ldapConfig->{$o}{'base'} eq $authBase) {
239
        my $filter = "o=$o";
240
        if (!$ldapConfig->{$o}{'org'}) {
241
            $ldapConfig->{$o}{'org'} = $filter;
242
        }
243
        if (!$ldapConfig->{$o}{'filter'}) {
244
            #$ldapConfig->{$o}{'filter'} = $filter;
245
            $ldapConfig->{$o}{'filter'} = $ldapConfig->{$o}{'org'};
246
        }
247
        # also include DN, which is just org + base
248
        if ($ldapConfig->{$o}{'org'}) {
249
            $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'org'} . "," . $ldapConfig->{$o}{'base'};
250
        }
251
    } else {
252
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'base'};
253
    }
254
    
255
    # set LDAP administrator user account
256
    if (!$ldapConfig->{$o}{'user'}) {
257
        $ldapConfig->{$o}{'user'} = $ldapConfig->{'unaffiliated'}{'user'};
258
    }
259
    # check for a fully qualified LDAP name. If it doesn't exist, append base.
260
    my @userParts = split(',', $ldapConfig->{$o}{'user'});
261
    if (scalar(@userParts) == 1) {
262
        $ldapConfig->{$o}{'user'} = $ldapConfig->{$o}{'user'} . "," . $ldapConfig->{$o}{'base'};
263
    }
264

    
265
    if (!$ldapConfig->{$o}{'password'}) {
266
        $ldapConfig->{$o}{'password'} = $ldapConfig->{'unaffiliated'}{'password'};
267
    }
268
}
269

    
270
### Determine the display organization list (such as NCEAS, Account ) in the ldap template files
271
my $displayOrgListStr;
272
$displayOrgListStr = $skinProperties->getProperty("ldap.templates.organizationList") or $displayOrgListStr = $properties->getProperty('ldap.templates.organizationList');
273
debug("the string of the org from properties : " . $displayOrgListStr);
274
my @displayOrgList = split(';', $displayOrgListStr);
275

    
276
my @validDisplayOrgList; #this array contains the org list which will be shown in the templates files.
277

    
278
my %orgNamesHash = %$orgNames;
279
foreach my $element (@displayOrgList) {
280
    if(exists $orgNamesHash{$element}) {
281
         my $label = $ldapConfig->{$element}{'label'};
282
         my %displayHash;
283
         $displayHash{$element} = $label;
284
         debug("push a hash containing the key " . $element . "with the value label" . $label . " into the display array");
285
         #if the name is found in the organization part of metacat.properties, put it into the valid array
286
         push(@validDisplayOrgList, \%displayHash);
287
    } 
288
    
289
}
290

    
291
if(!@validDisplayOrgList) {
292
     my $sender;
293
     $sender = $skinProperties->getProperty("email.sender") or $sender = $properties->getProperty('email.sender');
294
    print "Content-type: text/html\n\n";
295
    print "The value of property ldap.templates.organizationList in " 
296
     . $skinName . ".properties file or metacat.properties file (if the property doesn't exist in the " 
297
     . $skinName . ".properties file) is invalid. Please send the information to ". $sender;
298
    exit(0);
299
}
300

    
301

    
302
#--------------------------------------------------------------------------80c->
303
# Define the main program logic that calls subroutines to do the work
304
#--------------------------------------------------------------------------80c->
305

    
306
# The processing step we are handling
307
my $stage = $query->param('stage') || $templates->{'stage'};
308

    
309
my $cfg = $query->param('cfg');
310
debug("started with stage $stage, cfg $cfg");
311

    
312
# define the possible stages
313
my %stages = (
314
              'initregister'      => \&handleInitRegister,
315
              'register'          => \&handleRegister,
316
              'registerconfirmed' => \&handleRegisterConfirmed,
317
              'simplesearch'      => \&handleSimpleSearch,
318
              'initaddentry'      => \&handleInitAddEntry,
319
              'addentry'          => \&handleAddEntry,
320
              'initmodifyentry'   => \&handleInitModifyEntry,
321
              'modifyentry'       => \&handleModifyEntry,
322
              'changepass'        => \&handleChangePassword,
323
              'initchangepass'    => \&handleInitialChangePassword,
324
              'resetpass'         => \&handleResetPassword,
325
              'initresetpass'     => \&handleInitialResetPassword,
326
              'emailverification' => \&handleEmailVerification,
327
              'lookupname'        => \&handleLookupName,
328
              'searchnamesbyemail'=> \&handleSearchNameByEmail,
329
              #'getnextuid'        => \&getExistingHighestUidNum,
330
             );
331

    
332
# call the appropriate routine based on the stage
333
if ( $stages{$stage} ) {
334
  $stages{$stage}->();
335
} else {
336
  &handleResponseMessage();
337
}
338

    
339
#--------------------------------------------------------------------------80c->
340
# Define the subroutines to do the work
341
#--------------------------------------------------------------------------80c->
342

    
343
sub clearTemporaryAccounts {
344
	
345
    #search accounts that have expired
346
	my $org = $query->param('o'); 
347
    my $ldapUsername = $ldapConfig->{$org}{'user'};
348
    my $ldapPassword = $ldapConfig->{$org}{'password'};
349
    my $orgAuthBase = $ldapConfig->{$org}{'base'};
350
    my $orgExpiration = $ldapConfig->{$org}{'expiration'};
351
    my $tmpSearchBase = 'dc=tmp,' . $orgAuthBase; 
352
	
353
	my $dt = DateTime->now;
354
	$dt->subtract( hours => $orgExpiration );
355
	my $expirationDate = $dt->ymd("") . $dt->hms("") . "Z";
356
    my $filter = "(&(objectClass=inetOrgPerson)(createTimestamp<=" . $expirationDate . "))";
357
    debug("Clearing expired accounts with filter: " . $filter . ", base: " . $tmpSearchBase);    
358
    my @attrs = [ 'uid', 'o', 'ou', 'cn', 'mail', 'telephoneNumber', 'title' ];
359

    
360
    my $ldap;
361
    my $mesg;
362
    
363
    my $dn;
364

    
365
    #if main ldap server is down, a html file containing warning message will be returned
366
    debug("clearTemporaryAccounts: connecting to $ldapurl, $timeout");
367
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
368
    if ($ldap) {
369
    	$ldap->start_tls( verify => 'require',
370
                      cafile => $ldapServerCACertFile);
371
        $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword ); 
372
		$mesg = $ldap->search (
373
			base   => $tmpSearchBase,
374
			filter => $filter,
375
			attrs => \@attrs,
376
		);
377
	    if ($mesg->count() > 0) {
378
			my $entry;
379
			foreach $entry ($mesg->all_entries) { 
380
            	$dn = $entry->dn();
381
            	# remove the entry
382
   				debug("Removing expired account: " . $dn);
383
            	$ldap->delete($dn);
384
			}
385
        }
386
    	$ldap->unbind;   # take down session
387
    }
388

    
389
    return 0;
390
}
391

    
392
sub fullTemplate {
393
    my $templateList = shift;
394
    my $templateVars = setVars(shift);
395
    my $c = Captcha::reCAPTCHA->new;
396
    my $captcha = 'captcha';
397
    #my $error=null;
398
    my $use_ssl= 1;
399
    #my $options=null;
400
    # use the AJAX style, only need to provide the public key to the template
401
    $templateVars->{'recaptchaPublicKey'} = $recaptchaPublicKey;
402
    #$templateVars->{$captcha} = $c->get_html($recaptchaPublicKey,undef, $use_ssl, undef);
403
    $template->process( $templates->{'header'}, $templateVars );
404
    foreach my $tmpl (@{$templateList}) {
405
        $template->process( $templates->{$tmpl}, $templateVars );
406
    }
407
    $template->process( $templates->{'footer'}, $templateVars );
408
}
409

    
410

    
411
#
412
# Initialize a form for a user to request the account name associated with an email address
413
#
414
sub handleLookupName {
415
    
416
    print "Content-type: text/html\n\n";
417
    # process the template files:
418
    fullTemplate(['lookupName']); 
419
    exit();
420
}
421

    
422
#
423
# Handle the user's request to look up account names with a specified email address.
424
# This relates to "Forget your user name"
425
#
426
sub handleSearchNameByEmail{
427

    
428
    print "Content-type: text/html\n\n";
429
   
430
    my $allParams = {'mail' => $query->param('mail')};
431
    my @requiredParams = ('mail');
432
    if (! paramsAreValid(@requiredParams)) {
433
        my $errorMessage = "Required information is missing. " .
434
            "Please fill in all required fields and resubmit the form.";
435
        fullTemplate(['lookupName'], { allParams => $allParams,
436
                                     errorMessage => $errorMessage });
437
        exit();
438
    }
439
    my $mail = $query->param('mail');
440
    
441
    #search accounts with the specified emails 
442
    $searchBase = $authBase; 
443
    my $filter = "(mail=" . $mail . ")";
444
    my @attrs = [ 'uid', 'o', 'ou', 'cn', 'mail', 'telephoneNumber', 'title' ];
445
    my $notHtmlFormat = 1;
446
    my $found = findExistingAccounts($ldapurl, $searchBase, $filter, \@attrs, $notHtmlFormat);
447
    my $accountInfo;
448
    if ($found) {
449
        $accountInfo = $found;
450
    } else {
451
        $accountInfo = "There are no accounts associated with the email " . $mail . ".\n";
452
    }
453

    
454
    my $mailhost = $properties->getProperty('email.mailhost');
455
    my $sender;
456
    $sender = $skinProperties->getProperty("email.sender") or $sender = $properties->getProperty('email.sender');
457
    debug("the sender is " . $sender);
458
    my $recipient = $query->param('mail');
459
    # Send the email message to them
460
    my $smtp = Net::SMTP->new($mailhost) or do {  
461
                                                  fullTemplate( ['lookupName'], {allParams => $allParams, 
462
                                                                errorMessage => "Our mail server currently is experiencing some difficulties. Please contact " . 
463
                                                                $skinProperties->getProperty("email.recipient") . "." });  
464
                                                  exit(0);
465
                                               };
466
    $smtp->mail($sender);
467
    $smtp->to($recipient);
468

    
469
    my $message = <<"     ENDOFMESSAGE";
470
    To: $recipient
471
    From: $sender
472
    Subject: Your Account Information
473
        
474
    Somebody (hopefully you) looked up the account information associated with the email address.  
475
    Here is the account information:
476
    
477
    $accountInfo
478

    
479
    Thanks,
480
        $sender
481
    
482
     ENDOFMESSAGE
483
     $message =~ s/^[ \t\r\f]+//gm;
484
    
485
     $smtp->data($message);
486
     $smtp->quit;
487
     fullTemplate( ['lookupNameSuccess'] );
488
    
489
}
490

    
491

    
492
#
493
# create the initial registration form 
494
#
495
sub handleInitRegister {
496
  my $vars = shift;
497
  print "Content-type: text/html\n\n";
498
  # process the template files:
499
  fullTemplate(['register'], {stage => "register"}); 
500
  exit();
501
}
502

    
503

    
504

    
505
#
506
# process input from the register stage, which occurs when
507
# a user submits form data to create a new account
508
#
509
sub handleRegister {
510
    
511
    #print "Content-type: text/html\n\n";
512
    if ($query->param('o') =~ "LTER") {
513
      print "Content-type: text/html\n\n";
514
      fullTemplate( ['registerLter'] );
515
      exit(0);
516
    } 
517
    
518
    my $allParams = { 'givenName' => $query->param('givenName'), 
519
                      'sn' => $query->param('sn'),
520
                      'o' => $query->param('o'), 
521
                      'mail' => $query->param('mail'), 
522
                      'uid' => $query->param('uid'), 
523
                      'userPassword' => $query->param('userPassword'), 
524
                      'userPassword2' => $query->param('userPassword2'), 
525
                      'title' => $query->param('title'), 
526
                      'telephoneNumber' => $query->param('telephoneNumber') };
527
    
528
    # Check the recaptcha
529
    my $c = Captcha::reCAPTCHA->new;
530
    my $challenge = $query->param('recaptcha_challenge_field');
531
    my $response = $query->param('recaptcha_response_field');
532
    # Verify submission
533
    my $result = $c->check_answer(
534
        $recaptchaPrivateKey, $ENV{'REMOTE_ADDR'},
535
        $challenge, $response
536
    );
537

    
538
    if ( $result->{is_valid} ) {
539
        #print "Yes!";
540
        #exit();
541
    }
542
    else {
543
        print "Content-type: text/html\n\n";
544
        my $errorMessage = "The verification code is wrong. Please input again.";
545
        fullTemplate(['register'], { stage => "register",
546
                                     allParams => $allParams,
547
                                     errorMessage => $errorMessage });
548
        exit();
549
    }
550
    
551
    
552
    # Check that all required fields are provided and not null
553
    my @requiredParams = ( 'givenName', 'sn', 'o', 'mail', 
554
                           'uid', 'userPassword', 'userPassword2');
555
    if (! paramsAreValid(@requiredParams)) {
556
        print "Content-type: text/html\n\n";
557
        my $errorMessage = "Required information is missing. " .
558
            "Please fill in all required fields and resubmit the form.";
559
        fullTemplate(['register'], { stage => "register",
560
                                     allParams => $allParams,
561
                                     errorMessage => $errorMessage });
562
        exit();
563
    } else {
564
         if ($query->param('userPassword') ne $query->param('userPassword2')) {
565
            print "Content-type: text/html\n\n";
566
            my $errorMessage = "The passwords do not match. Try again.";
567
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
568
                                                            allParams => $allParams,
569
                                                            errorMessage => $errorMessage });
570
            exit();
571
        }
572
        my $o = $query->param('o');    
573
        $searchBase = $ldapConfig->{$o}{'base'};  
574
    }
575
    
576
    # Remove any expired temporary accounts for this subtree before continuing
577
    clearTemporaryAccounts();
578
    
579
    # Check if the uid was taken in the production space
580
    my @attrs = [ 'uid', 'o', 'ou', 'cn', 'mail', 'telephoneNumber', 'title' ];
581
    my $uidExists;
582
    my $uid=$query->param('uid');
583
    my $uidFilter = "uid=" . $uid;
584
    my $newSearchBase = $ldapConfig->{$query->param('o')}{'org'} . "," .  $searchBase;
585
    debug("the new search base is $newSearchBase");
586
    $uidExists = uidExists($ldapurl, $newSearchBase, $uidFilter, \@attrs);
587
    debug("the result of uidExists $uidExists");
588
    if($uidExists) {
589
         print "Content-type: text/html\n\n";
590
            my $errorMessage = $uidExists;
591
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
592
                                                            allParams => $allParams,
593
                                                            errorMessage => $errorMessage });
594
            exit();
595
    }
596

    
597
    # Search LDAP for matching entries that already exist
598
    # Some forms use a single text search box, whereas others search per
599
    # attribute.
600
    my $filter;
601
    if ($query->param('searchField')) {
602

    
603
      $filter = "(|" . 
604
                "(uid=" . $query->param('searchField') . ") " .
605
                "(mail=" . $query->param('searchField') . ")" .
606
                "(&(sn=" . $query->param('searchField') . ") " . 
607
                "(givenName=" . $query->param('searchField') . "))" . 
608
                ")";
609
    } else {
610
      $filter = "(|" . 
611
                "(uid=" . $query->param('uid') . ") " .
612
                "(mail=" . $query->param('mail') . ")" .
613
                "(&(sn=" . $query->param('sn') . ") " . 
614
                "(givenName=" . $query->param('givenName') . "))" . 
615
                ")";
616
    }
617

    
618
    
619
    my $found = findExistingAccounts($ldapurl, $searchBase, $filter, \@attrs);
620

    
621
    # If entries match, send back a request to confirm new-user creation
622
    if ($found) {
623
      print "Content-type: text/html\n\n";
624
      fullTemplate( ['registerMatch', 'register'], { stage => "registerconfirmed",
625
                                                     allParams => $allParams,
626
                                                     foundAccounts => $found });
627
    # Otherwise, create a new user in the LDAP directory
628
    } else {
629
        createTemporaryAccount($allParams);
630
    }
631

    
632
    exit();
633
}
634

    
635
#
636
# process input from the registerconfirmed stage, which occurs when
637
# a user chooses to create an account despite similarities to other
638
# existing accounts
639
#
640
sub handleRegisterConfirmed {
641
  
642
    my $allParams = { 'givenName' => $query->param('givenName'), 
643
                      'sn' => $query->param('sn'),
644
                      'o' => $query->param('o'), 
645
                      'mail' => $query->param('mail'), 
646
                      'uid' => $query->param('uid'), 
647
                      'userPassword' => $query->param('userPassword'), 
648
                      'userPassword2' => $query->param('userPassword2'), 
649
                      'title' => $query->param('title'), 
650
                      'telephoneNumber' => $query->param('telephoneNumber') };
651
    #print "Content-type: text/html\n\n";
652
    createTemporaryAccount($allParams);
653
    exit();
654
}
655

    
656
#
657
# change a user's password upon request
658
#
659
sub handleChangePassword {
660

    
661
    print "Content-type: text/html\n\n";
662

    
663
    my $allParams = { 'test' => "1", };
664
    if ($query->param('uid')) {
665
        $$allParams{'uid'} = $query->param('uid');
666
    }
667
    if ($query->param('o')) {
668
        $$allParams{'o'} = $query->param('o');
669
        my $o = $query->param('o');
670
        
671
        $searchBase = $ldapConfig->{$o}{'base'};
672
    }
673

    
674

    
675
    # Check that all required fields are provided and not null
676
    my @requiredParams = ( 'uid', 'o', 'oldpass', 
677
                           'userPassword', 'userPassword2');
678
    if (! paramsAreValid(@requiredParams)) {
679
        my $errorMessage = "Required information is missing. " .
680
            "Please fill in all required fields and submit the form.";
681
        fullTemplate( ['changePass'], { stage => "changepass",
682
                                        allParams => $allParams,
683
                                        errorMessage => $errorMessage });
684
        exit();
685
    }
686

    
687
    # We have all of the info we need, so try to change the password
688
    if ($query->param('userPassword') eq $query->param('userPassword2')) {
689

    
690
        my $o = $query->param('o');
691
        $searchBase = $ldapConfig->{$o}{'base'};
692
        $ldapUsername = $ldapConfig->{$o}{'user'};
693
        $ldapPassword = $ldapConfig->{$o}{'password'};
694

    
695
        my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};;
696
        if ($query->param('o') =~ "LTER") {
697
            fullTemplate( ['registerLter'] );
698
        } else {
699
            my $errorMessage = changePassword(
700
                    $dn, $query->param('userPassword'), 
701
                    $dn, $query->param('oldpass'), $query->param('o'));
702
            if ($errorMessage) {
703
                fullTemplate( ['changePass'], { stage => "changepass",
704
                                                allParams => $allParams,
705
                                                errorMessage => $errorMessage });
706
                exit();
707
            } else {
708
                fullTemplate( ['changePassSuccess'], { stage => "changepass",
709
                                                       allParams => $allParams });
710
                exit();
711
            }
712
        }
713
    } else {
714
        my $errorMessage = "The passwords do not match. Try again.";
715
        fullTemplate( ['changePass'], { stage => "changepass",
716
                                        allParams => $allParams,
717
                                        errorMessage => $errorMessage });
718
        exit();
719
    }
720
}
721

    
722
#
723
# change a user's password upon request - no input params
724
# only display chagepass template without any error
725
#
726
sub handleInitialChangePassword {
727
    print "Content-type: text/html\n\n";
728

    
729
    my $allParams = { 'test' => "1", };
730
    my $errorMessage = "";
731
    fullTemplate( ['changePass'], { stage => "changepass",
732
                                    errorMessage => $errorMessage });
733
    exit();
734
}
735

    
736
#
737
# reset a user's password upon request
738
#
739
sub handleResetPassword {
740

    
741
    print "Content-type: text/html\n\n";
742

    
743
    my $allParams = { 'test' => "1", };
744
    if ($query->param('uid')) {
745
        $$allParams{'uid'} = $query->param('uid');
746
    }
747
    if ($query->param('o')) {
748
        $$allParams{'o'} = $query->param('o');
749
        my $o = $query->param('o');
750
        
751
        $searchBase = $ldapConfig->{$o}{'base'};
752
        $ldapUsername = $ldapConfig->{$o}{'user'};
753
        $ldapPassword = $ldapConfig->{$o}{'password'};
754
    }
755

    
756
    # Check that all required fields are provided and not null
757
    my @requiredParams = ( 'uid', 'o' );
758
    if (! paramsAreValid(@requiredParams)) {
759
        my $errorMessage = "Required information is missing. " .
760
            "Please fill in all required fields and submit the form.";
761
        fullTemplate( ['resetPass'],  { stage => "resetpass",
762
                                        allParams => $allParams,
763
                                        errorMessage => $errorMessage });
764
        exit();
765
    }
766

    
767
    # We have all of the info we need, so try to change the password
768
    my $o = $query->param('o');
769
    my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};
770
    debug("handleResetPassword: dn: $dn");
771
    if ($query->param('o') =~ "LTER") {
772
        fullTemplate( ['registerLter'] );
773
        exit();
774
    } else {
775
        my $errorMessage = "";
776
        my $recipient;
777
        my $userPass;
778
        my $entry = getLdapEntry($ldapurl, $searchBase, 
779
                $query->param('uid'), $query->param('o'));
780

    
781
        if ($entry) {
782
            $recipient = $entry->get_value('mail');
783
            $userPass = getRandomPassword();
784
            $errorMessage = changePassword($dn, $userPass, $ldapUsername, $ldapPassword, $query->param('o'));
785
        } else {
786
            $errorMessage = "User not found in database.  Please try again.";
787
        }
788

    
789
        if ($errorMessage) {
790
            fullTemplate( ['resetPass'], { stage => "resetpass",
791
                                           allParams => $allParams,
792
                                           errorMessage => $errorMessage });
793
            exit();
794
        } else {
795
            my $errorMessage = sendPasswordNotification($query->param('uid'),
796
                    $query->param('o'), $userPass, $recipient, $cfg);
797
            fullTemplate( ['resetPassSuccess'], { stage => "resetpass",
798
                                                  allParams => $allParams,
799
                                                  errorMessage => $errorMessage });
800
            exit();
801
        }
802
    }
803
}
804

    
805
#
806
# reset a user's password upon request- no initial params
807
# only display resetpass template without any error
808
#
809
sub handleInitialResetPassword {
810
    print "Content-type: text/html\n\n";
811
    my $errorMessage = "";
812
    fullTemplate( ['resetPass'], { stage => "resetpass",
813
                                   errorMessage => $errorMessage });
814
    exit();
815
}
816

    
817
#
818
# Construct a random string to use for a newly reset password
819
#
820
sub getRandomPassword {
821
    my $length = shift;
822
    if (!$length) {
823
        $length = 8;
824
    }
825
    my $newPass = "";
826

    
827
    my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ ^) );
828
    $newPass = join("", @chars[ map { rand @chars } ( 1 .. $length ) ]);
829
    return $newPass;
830
}
831

    
832
#
833
# Change a password to a new value, binding as the provided user
834
#
835
sub changePassword {
836
    my $userDN = shift;
837
    my $userPass = shift;
838
    my $bindDN = shift;
839
    my $bindPass = shift;
840
    my $o = shift;
841

    
842
    my $searchBase = $ldapConfig->{$o}{'base'};
843

    
844
    my $errorMessage = 0;
845
    my $ldap;
846

    
847
    #if main ldap server is down, a html file containing warning message will be returned
848
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
849
    
850
    if ($ldap) {
851
        $ldap->start_tls( verify => 'require',
852
                      cafile => $ldapServerCACertFile);
853
        debug("changePassword: attempting to bind to $bindDN");
854
        my $bindresult = $ldap->bind( version => 3, dn => $bindDN, 
855
                                  password => $bindPass );
856
        if ($bindresult->code) {
857
            $errorMessage = "Failed to log in. Are you sure your connection credentails are " .
858
                            "correct? Please correct and try again...";
859
            return $errorMessage;
860
        }
861

    
862
    	# Find the user here and change their entry
863
    	my $newpass = createSeededPassHash($userPass);
864
    	my $modifications = { userPassword => $newpass };
865
      debug("changePass: setting password for $userDN to $newpass");
866
    	my $result = $ldap->modify( $userDN, replace => { %$modifications });
867
    
868
    	if ($result->code()) {
869
            debug("changePass: error changing password: " . $result->error);
870
        	$errorMessage = "There was an error changing the password:" .
871
                           "<br />\n" . $result->error;
872
    	} 
873
    	$ldap->unbind;   # take down session
874
    }
875

    
876
    return $errorMessage;
877
}
878

    
879
#
880
# generate a Seeded SHA1 hash of a plaintext password
881
#
882
sub createSeededPassHash {
883
    my $secret = shift;
884

    
885
    my $salt = "";
886
    for (my $i=0; $i < 4; $i++) {
887
        $salt .= int(rand(10));
888
    }
889

    
890
    my $ctx = Digest::SHA1->new;
891
    $ctx->add($secret);
892
    $ctx->add($salt);
893
    my $hashedPasswd = '{SSHA}' . encode_base64($ctx->digest . $salt ,'');
894

    
895
    return $hashedPasswd;
896
}
897

    
898
#
899
# Look up an ldap entry for a user
900
#
901
sub getLdapEntry {
902
    my $ldapurl = shift;
903
    my $base = shift;
904
    my $username = shift;
905
    my $org = shift;
906

    
907
    my $entry = "";
908
    my $mesg;
909
    my $ldap;
910
    debug("ldap server: $ldapurl");
911

    
912
    #if main ldap server is down, a html file containing warning message will be returned
913
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
914
    
915
    if ($ldap) {
916
        $ldap->start_tls( verify => 'none');
917
        #$ldap->start_tls( verify => 'require',
918
        #              cafile => $ldapServerCACertFile);
919
    	my $bindresult = $ldap->bind;
920
    	if ($bindresult->code) {
921
        	return $entry;
922
    	}
923

    
924
        $base = $ldapConfig->{$org}{'org'} . ',' . $base;
925
        debug("getLdapEntry, searching for $base, (uid=$username)");
926
        $mesg = $ldap->search ( base   => $base, filter => "(uid=$username)");
927
    	#if($ldapConfig->{$org}{'filter'}){
928
            #debug("getLdapEntry: filter set, searching for base=$base, " .
929
                  #"(&(uid=$username)($ldapConfig->{$org}{'filter'}))");
930
        	#$mesg = $ldap->search ( base   => $base,
931
                #filter => "(&(uid=$username)($ldapConfig->{$org}{'filter'}))");
932
    	#} else {
933
            #debug("getLdapEntry: no filter, searching for $base, (uid=$username)");
934
        	#$mesg = $ldap->search ( base   => $base, filter => "(uid=$username)");
935
    	#}
936
    
937
    	if ($mesg->count > 0) {
938
        	$entry = $mesg->pop_entry;
939
        	$ldap->unbind;   # take down session
940
    	} else {
941
        	$ldap->unbind;   # take down session
942
        	# Follow references by recursive call to self
943
        	my @references = $mesg->references();
944
        	for (my $i = 0; $i <= $#references; $i++) {
945
            	my $uri = URI->new($references[$i]);
946
            	my $host = $uri->host();
947
            	my $path = $uri->path();
948
            	$path =~ s/^\///;
949
            	$entry = &getLdapEntry($host, $path, $username, $org);
950
            	if ($entry) {
951
                    debug("getLdapEntry: recursion found $host, $path, $username, $org");
952
                	return $entry;
953
            	}
954
        	}
955
    	}
956
    }
957
    return $entry;
958
}
959

    
960
# 
961
# send an email message notifying the user of the pw change
962
#
963
sub sendPasswordNotification {
964
    my $username = shift;
965
    my $org = shift;
966
    my $newPass = shift;
967
    my $recipient = shift;
968
    my $cfg = shift;
969

    
970
    my $errorMessage = "";
971
    if ($recipient) {
972
    
973
        my $mailhost = $properties->getProperty('email.mailhost');
974
        my $sender;
975
        $sender = $skinProperties->getProperty("email.sender") or $sender = $properties->getProperty('email.sender');
976
        # Send the email message to them
977
        my $smtp = Net::SMTP->new($mailhost);
978
        $smtp->mail($sender);
979
        $smtp->to($recipient);
980

    
981
        my $message = <<"        ENDOFMESSAGE";
982
        To: $recipient
983
        From: $sender
984
        Subject: Your Account Password Reset
985
        
986
        Somebody (hopefully you) requested that your account password be reset.  
987
        Your temporary password is below. Please change it as soon as possible 
988
        at: $contextUrl/style/skins/account/.
989

    
990
            Username: $username
991
        Organization: $org
992
        New Password: $newPass
993

    
994
        Thanks,
995
            $sender
996
    
997
        ENDOFMESSAGE
998
        $message =~ s/^[ \t\r\f]+//gm;
999
    
1000
        $smtp->data($message);
1001
        $smtp->quit;
1002
    } else {
1003
        $errorMessage = "Failed to send password because I " .
1004
                        "couldn't find a valid email address.";
1005
    }
1006
    return $errorMessage;
1007
}
1008

    
1009
#
1010
# search the LDAP production space to see if a uid already exists
1011
#
1012
sub uidExists {
1013
    my $ldapurl = shift;
1014
    debug("the ldap ulr is $ldapurl");
1015
    my $base = shift;
1016
    debug("the base is $base");
1017
    my $filter = shift;
1018
    debug("the filter is $filter");
1019
    my $attref = shift;
1020
  
1021
    my $ldap;
1022
    my $mesg;
1023

    
1024
    my $foundAccounts = 0;
1025

    
1026
    #if main ldap server is down, a html file containing warning message will be returned
1027
    debug("uidExists: connecting to $ldapurl, $timeout");
1028
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1029
    if ($ldap) {
1030
        $ldap->start_tls( verify => 'none');
1031
        #$ldap->start_tls( verify => 'require',
1032
        #              cafile => $ldapServerCACertFile);
1033
        $ldap->bind( version => 3, anonymous => 1);
1034
        $mesg = $ldap->search (
1035
            base   => $base,
1036
            filter => $filter,
1037
            attrs => @$attref,
1038
        );
1039
        debug("the message count is " . $mesg->count());
1040
        if ($mesg->count() > 0) {
1041
            $foundAccounts = "The username has been taken already by another user. Please choose a different one.";
1042
           
1043
        }
1044
        $ldap->unbind;   # take down session
1045
    } else {
1046
        $foundAccounts = "The ldap server is not running";
1047
    }
1048
    return $foundAccounts;
1049
}
1050

    
1051
#
1052
# search the LDAP directory to see if a similar account already exists
1053
#
1054
sub findExistingAccounts {
1055
    my $ldapurl = shift;
1056
    my $base = shift;
1057
    my $filter = shift;
1058
    my $attref = shift;
1059
    my $notHtmlFormat = shift;
1060
    my $ldap;
1061
    my $mesg;
1062

    
1063
    my $foundAccounts = 0;
1064

    
1065
    #if main ldap server is down, a html file containing warning message will be returned
1066
    debug("findExistingAccounts: connecting to $ldapurl, $timeout");
1067
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1068
    if ($ldap) {
1069
    	$ldap->start_tls( verify => 'none');
1070
    	#$ldap->start_tls( verify => 'require',
1071
        #              cafile => $ldapServerCACertFile);
1072
    	$ldap->bind( version => 3, anonymous => 1);
1073
		$mesg = $ldap->search (
1074
			base   => $base,
1075
			filter => $filter,
1076
			attrs => @$attref,
1077
		);
1078

    
1079
	    if ($mesg->count() > 0) {
1080
			$foundAccounts = "";
1081
			my $entry;
1082
			foreach $entry ($mesg->all_entries) { 
1083
                # a fix to ignore 'ou=Account' properties which are not usable accounts within Metacat.
1084
                # this could be done directly with filters on the LDAP connection, instead.
1085
                #if ($entry->dn !~ /ou=Account/) {
1086
                    if($notHtmlFormat) {
1087
                        $foundAccounts .= "\nAccount: ";
1088
                    } else {
1089
                        $foundAccounts .= "<p>\n<b><u>Account:</u> ";
1090
                    }
1091
                    $foundAccounts .= $entry->dn();
1092
                    if($notHtmlFormat) {
1093
                        $foundAccounts .= "\n";
1094
                    } else {
1095
                        $foundAccounts .= "</b><br />\n";
1096
                    }
1097
                    foreach my $attribute ($entry->attributes()) {
1098
                        my $value = $entry->get_value($attribute);
1099
                        $foundAccounts .= "$attribute: ";
1100
                        $foundAccounts .= $value;
1101
                         if($notHtmlFormat) {
1102
                            $foundAccounts .= "\n";
1103
                        } else {
1104
                            $foundAccounts .= "<br />\n";
1105
                        }
1106
                    }
1107
                    if($notHtmlFormat) {
1108
                        $foundAccounts .= "\n";
1109
                    } else {
1110
                        $foundAccounts .= "</p>\n";
1111
                    }
1112
                    
1113
                #}
1114
			}
1115
        }
1116
    	$ldap->unbind;   # take down session
1117

    
1118
    	# Follow references
1119
    	my @references = $mesg->references();
1120
    	for (my $i = 0; $i <= $#references; $i++) {
1121
        	my $uri = URI->new($references[$i]);
1122
        	my $host = $uri->host();
1123
        	my $path = $uri->path();
1124
        	$path =~ s/^\///;
1125
        	my $refFound = &findExistingAccounts($host, $path, $filter, $attref, $notHtmlFormat);
1126
        	if ($refFound) {
1127
            	$foundAccounts .= $refFound;
1128
        	}
1129
    	}
1130
    }
1131

    
1132
    #print "<p>Checking referrals...</p>\n";
1133
    #my @referrals = $mesg->referrals();
1134
    #print "<p>Referrals count: ", scalar(@referrals), "</p>\n";
1135
    #for (my $i = 0; $i <= $#referrals; $i++) {
1136
        #print "<p>Referral: ", $referrals[$i], "</p>\n";
1137
    #}
1138

    
1139
    return $foundAccounts;
1140
}
1141

    
1142
#
1143
# Validate that we have the proper set of input parameters
1144
#
1145
sub paramsAreValid {
1146
    my @pnames = @_;
1147

    
1148
    my $allValid = 1;
1149
    foreach my $parameter (@pnames) {
1150
        if (!defined($query->param($parameter)) || 
1151
            ! $query->param($parameter) ||
1152
            $query->param($parameter) =~ /^\s+$/) {
1153
            $allValid = 0;
1154
        }
1155
    }
1156

    
1157
    return $allValid;
1158
}
1159

    
1160
#
1161
# Create a temporary account for a user and send an email with a link which can click for the
1162
# verification. This is used to protect the ldap server against spams.
1163
#
1164
sub createTemporaryAccount {
1165
    my $allParams = shift;
1166
    my $org = $query->param('o'); 
1167
    my $ldapUsername = $ldapConfig->{$org}{'user'};
1168
    my $ldapPassword = $ldapConfig->{$org}{'password'};
1169
    my $tmp = 1;
1170

    
1171
    ################## Search LDAP to see if the dc=tmp which stores the inactive accounts exist or not. If it doesn't exist, it will be generated
1172
    my $orgAuthBase = $ldapConfig->{$org}{'base'};
1173
    my $tmpSearchBase = 'dc=tmp,' . $orgAuthBase; 
1174
    my $tmpFilter = "dc=tmp";
1175
    my @attributes=['dc'];
1176
    my $foundTmp = searchDirectory($ldapurl, $orgAuthBase, $tmpFilter, \@attributes);
1177
    if (!$foundTmp) {
1178
        my $dn = $tmpSearchBase;
1179
        my $additions = [ 
1180
                    'dc' => 'tmp',
1181
                    'o'  => 'tmp',
1182
                    'objectclass' => ['top', 'dcObject', 'organization']
1183
                    ];
1184
        createItem($dn, $ldapUsername, $ldapPassword, $additions, $tmp, $allParams);
1185
    } else {
1186
     debug("found the tmp space");
1187
    }
1188
    
1189
    ################## Search LDAP for matching o or ou under the dc=tmp that already exist. If it doesn't exist, it will be generated
1190
    my $filter = $ldapConfig->{$org}{'filter'};   
1191
    
1192
    debug("search filer " . $filter);
1193
    debug("ldap server ". $ldapurl);
1194
    debug("sesarch base " . $tmpSearchBase);
1195
    #print "Content-type: text/html\n\n";
1196
    my @attrs = ['o', 'ou' ];
1197
    my $found = searchDirectory($ldapurl, $tmpSearchBase, $filter, \@attrs);
1198

    
1199
    my @organizationInfo = split('=', $ldapConfig->{$org}{'org'}); #split 'o=NCEAS' or something like that
1200
    my $organization = $organizationInfo[0]; # This will be 'o' or 'ou'
1201
    my $organizationName = $organizationInfo[1]; # This will be 'NCEAS' or 'Account'
1202
        
1203
    if(!$found) {
1204
        debug("generate the subtree in the dc=tmp===========================");
1205
        #need to generate the subtree o or ou
1206
        my $additions;
1207
            if($organization eq 'ou') {
1208
                $additions = [ 
1209
                    $organization   => $organizationName,
1210
                    'objectclass' => ['top', 'organizationalUnit']
1211
                    ];
1212
            
1213
            } else {
1214
                $additions = [ 
1215
                    $organization   => $organizationName,
1216
                    'objectclass' => ['top', 'organization']
1217
                    ];
1218
            
1219
            } 
1220
        my $dn=$ldapConfig->{$org}{'org'} . ',' . $tmpSearchBase;
1221
        createItem($dn, $ldapUsername, $ldapPassword, $additions, $tmp, $allParams);
1222
    } 
1223
    
1224
    ################create an account under tmp subtree 
1225
    
1226
     my $dn_store_next_uid=$properties->getProperty('ldap.nextuid.storing.dn');
1227
    my $attribute_name_store_next_uid = $properties->getProperty('ldap.nextuid.storing.attributename');
1228
    #get the next avaliable uid number. If it fails, the program will exist.
1229
    my $nextUidNumber = getNextUidNumber($ldapUsername, $ldapPassword);
1230
    if(!$nextUidNumber) {
1231
        print "Content-type: text/html\n\n";
1232
         my $sender;
1233
        $sender = $skinProperties->getProperty("email.recipient") or $sender = $properties->getProperty('email.recipient');
1234
        my $errorMessage = "The Identity Service can't get the next avaliable uid number. Please try again.  If the issue persists, please contact the administrator - $sender.
1235
                           The possible reasons are: the dn - $dn_store_next_uid or its attribute - $attribute_name_store_next_uid don't exist; the value of the attribute - $attribute_name_store_next_uid
1236
                           is not a number; or lots of users were registering and you couldn't get a lock on the dn - $dn_store_next_uid.";
1237
        fullTemplate(['register'], { stage => "register",
1238
                                     allParams => $allParams,
1239
                                     errorMessage => $errorMessage });
1240
        exit(0);
1241
    }
1242
    my $cn = join(" ", $query->param('givenName'), $query->param('sn')); 
1243
    #generate a randomstr for matching the email.
1244
    my $randomStr = getRandomPassword(16);
1245
    # Create a hashed version of the password
1246
    my $shapass = createSeededPassHash($query->param('userPassword'));
1247
    my $additions = [ 
1248
                'uid'   => $query->param('uid'),
1249
                'cn'   => $cn,
1250
                'sn'   => $query->param('sn'),
1251
                'givenName'   => $query->param('givenName'),
1252
                'mail' => $query->param('mail'),
1253
                'userPassword' => $shapass,
1254
                'employeeNumber' => $randomStr,
1255
                'uidNumber' => $nextUidNumber,
1256
                'gidNumber' => $nextUidNumber,
1257
                'loginShell' => '/sbin/nologin',
1258
                'homeDirectory' => '/dev/null',
1259
                'objectclass' => ['top', 'person', 'organizationalPerson', 
1260
                                'inetOrgPerson', 'posixAccount', 'shadowAccount' ],
1261
                $organization   => $organizationName
1262
                ];
1263
    my $gecos;
1264
    if (defined($query->param('telephoneNumber')) && 
1265
                $query->param('telephoneNumber') &&
1266
                ! $query->param('telephoneNumber') =~ /^\s+$/) {
1267
                $$additions[$#$additions + 1] = 'telephoneNumber';
1268
                $$additions[$#$additions + 1] = $query->param('telephoneNumber');
1269
                $gecos = $cn . ',,'. $query->param('telephoneNumber'). ',';
1270
    } else {
1271
        $gecos = $cn . ',,,';
1272
    }
1273
    
1274
    $$additions[$#$additions + 1] = 'gecos';
1275
    $$additions[$#$additions + 1] = $gecos;
1276
    
1277
    if (defined($query->param('title')) && 
1278
                $query->param('title') &&
1279
                ! $query->param('title') =~ /^\s+$/) {
1280
                $$additions[$#$additions + 1] = 'title';
1281
                $$additions[$#$additions + 1] = $query->param('title');
1282
    }
1283

    
1284
    
1285
    #$$additions[$#$additions + 1] = 'o';
1286
    #$$additions[$#$additions + 1] = $org;
1287
    my $dn='uid=' . $query->param('uid') . ',' . $ldapConfig->{$org}{'org'} . ',' . $tmpSearchBase;
1288
    createItem($dn, $ldapUsername, $ldapPassword, $additions, $tmp, $allParams);
1289
    
1290
    
1291
    ####################send the verification email to the user
1292
    my $link = '/' . $context . '/cgi-bin/ldapweb.cgi?cfg=' . $skinName . '&' . 'stage=' . $emailVerification . '&' . 'dn=' . $dn . '&' . 'hash=' . $randomStr . '&o=' . $org . '&uid=' . $query->param('uid'); #even though we use o=something. The emailVerification will figure the real o= or ou=something.
1293
    
1294
    my $overrideURL;
1295
    $overrideURL = $skinProperties->getProperty("email.overrideURL");
1296
    debug("the overrideURL is $overrideURL");
1297
    if (defined($overrideURL) && !($overrideURL eq '')) {
1298
    	$link = $serverUrl . $overrideURL . $link;
1299
    } else {
1300
    	$link = $serverUrl . $link;
1301
    }
1302
    
1303
    my $mailhost = $properties->getProperty('email.mailhost');
1304
    my $sender;
1305
    $sender = $skinProperties->getProperty("email.sender") or $sender = $properties->getProperty('email.sender');
1306
    debug("the sender is " . $sender);
1307
    my $recipient = $query->param('mail');
1308
    # Send the email message to them
1309
    my $smtp = Net::SMTP->new($mailhost) or do {  
1310
                                                  fullTemplate( ['registerFailed'], {errorMessage => "The temporary account " . $dn . " was created successfully. However, the vertification email can't be sent to you because the email server has some issues. Please contact " . 
1311
                                                  $skinProperties->getProperty("email.recipient") . "." });  
1312
                                                  exit(0);
1313
                                               };
1314
    $smtp->mail($sender);
1315
    $smtp->to($recipient);
1316

    
1317
    my $message = <<"     ENDOFMESSAGE";
1318
    To: $recipient
1319
    From: $sender
1320
    Subject: New Account Activation
1321
        
1322
    Somebody (hopefully you) registered an account on $contextUrl/style/skins/account/.  
1323
    Please click the following link to activate your account.
1324
    If the link doesn't work, please copy the link to your browser:
1325
    
1326
    $link
1327

    
1328
    Thanks,
1329
        $sender
1330
    
1331
     ENDOFMESSAGE
1332
     $message =~ s/^[ \t\r\f]+//gm;
1333
    
1334
     $smtp->data($message);
1335
     $smtp->quit;
1336
    debug("the link is " . $link);
1337
    fullTemplate( ['success'] );
1338
    
1339
}
1340

    
1341
#
1342
# Bind to LDAP and create a new item (a user or subtree) using the information provided
1343
# by the user
1344
#
1345
sub createItem {
1346
    my $dn = shift;
1347
    my $ldapUsername = shift;
1348
    my $ldapPassword = shift;
1349
    my $additions = shift;
1350
    my $temp = shift; #if it is for a temporary account.
1351
    my $allParams = shift;
1352
    
1353
    my @failureTemplate;
1354
    if($temp){
1355
        @failureTemplate = ['registerFailed', 'register'];
1356
    } else {
1357
        @failureTemplate = ['registerFailed'];
1358
    }
1359
    print "Content-type: text/html\n\n";
1360
    debug("the dn is " . $dn);
1361
    debug("LDAP connection to $ldapurl...");    
1362
    debug("the ldap ca certificate is " . $ldapServerCACertFile);
1363
    #if main ldap server is down, a html file containing warning message will be returned
1364
    my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1365
    if ($ldap) {
1366
            $ldap->start_tls( verify => 'require',
1367
                      cafile => $ldapServerCACertFile);
1368
            debug("Attempting to bind to LDAP server with dn = $ldapUsername, pwd = $ldapPassword");
1369
            $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword ); 
1370
            my $result = $ldap->add ( 'dn' => $dn, 'attr' => [@$additions ]);
1371
            if ($result->code()) {
1372
                fullTemplate(@failureTemplate, { stage => "register",
1373
                                                            allParams => $allParams,
1374
                                                            errorMessage => $result->error });
1375
                exist(0);
1376
                # TODO SCW was included as separate errors, test this
1377
                #$templateVars    = setVars({ stage => "register",
1378
                #                     allParams => $allParams });
1379
                #$template->process( $templates->{'register'}, $templateVars);
1380
            } else {
1381
                #fullTemplate( ['success'] );
1382
            }
1383
            $ldap->unbind;   # take down session
1384
            
1385
    } else {   
1386
         fullTemplate(@failureTemplate, { stage => "register",
1387
                                                            allParams => $allParams,
1388
                                                            errorMessage => "The ldap server is not available now. Please try it later"});
1389
         exit(0);
1390
    }
1391
  
1392
}
1393

    
1394

    
1395

    
1396

    
1397

    
1398

    
1399
#
1400
# This subroutine will handle a email verification:
1401
# If the hash string matches the one store in the ldap, the account will be
1402
# copied from the temporary space to the permanent tree and the account in 
1403
# the temporary space will be removed.
1404
sub handleEmailVerification {
1405

    
1406
    my $cfg = $query->param('cfg');
1407
    my $dn = $query->param('dn');
1408
    my $hash = $query->param('hash');
1409
    my $org = $query->param('o');
1410
    my $uid = $query->param('uid');
1411
    
1412
    my $ldapUsername;
1413
    my $ldapPassword;
1414
    #my $orgAuthBase;
1415

    
1416
    $ldapUsername = $ldapConfig->{$org}{'user'};
1417
    $ldapPassword = $ldapConfig->{$org}{'password'};
1418
    #$orgAuthBase = $ldapConfig->{$org}{'base'};
1419
    
1420
    debug("LDAP connection to $ldapurl...");    
1421
    
1422

    
1423
   print "Content-type: text/html\n\n";
1424
   #if main ldap server is down, a html file containing warning message will be returned
1425
   my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1426
   if ($ldap) {
1427
        $ldap->start_tls( verify => 'require',
1428
                      cafile => $ldapServerCACertFile);
1429
        $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword );
1430
        my $mesg = $ldap->search(base => $dn, scope => 'base', filter => '(objectClass=*)'); #This dn is with the dc=tmp. So it will find out the temporary account registered in registration step.
1431
        my $max = $mesg->count;
1432
        debug("the count is " . $max);
1433
        if($max < 1) {
1434
            $ldap->unbind;   # take down session
1435
            fullTemplate( ['verificationFailed'], {errorMessage => "No record matched the dn " . $dn . " for the activation. You probably already activated the account."});
1436
            #handleLDAPBindFailure($ldapurl);
1437
            exit(0);
1438
        } else {
1439
            #check if the hash string match
1440
            my $entry = $mesg->entry (0);
1441
            my $hashStrFromLdap = $entry->get_value('employeeNumber');
1442
            if( $hashStrFromLdap eq $hash) {
1443
                #my $additions = [ ];
1444
                #foreach my $attr ( $entry->attributes ) {
1445
                    #if($attr ne 'employeeNumber') {
1446
                        #$$additions[$#$additions + 1] = $attr;
1447
                        #$$additions[$#$additions + 1] = $entry->get_value( $attr );
1448
                    #}
1449
                #}
1450

    
1451
                
1452
                my $orgDn = $ldapConfig->{$org}{'dn'}; #the DN for the organization.
1453
                $mesg = $ldap->moddn(
1454
                            dn => $dn,
1455
                            deleteoldrdn => 1,
1456
                            newrdn => "uid=" . $uid,
1457
                            newsuperior  =>  $orgDn);
1458
                $ldap->unbind;   # take down session
1459
                if($mesg->code()) {
1460
                    fullTemplate( ['verificationFailed'], {errorMessage => "Cannot move the account from the inactive area to the ative area since " . $mesg->error()});
1461
                    exit(0);
1462
                } else {
1463
                    fullTemplate( ['verificationSuccess'] );
1464
                }
1465
                #createAccount2($dn, $ldapUsername, $ldapPassword, $additions, $tmp, $allParams);
1466
            } else {
1467
                $ldap->unbind;   # take down session
1468
                fullTemplate( ['verificationFailed'], {errorMessage => "The hash string " . $hash . " from your link doesn't match our record."});
1469
                exit(0);
1470
            }
1471
            
1472
        }
1473
    } else {   
1474
        handleLDAPBindFailure($ldapurl);
1475
        exit(0);
1476
    }
1477

    
1478
}
1479

    
1480
sub handleResponseMessage {
1481

    
1482
  print "Content-type: text/html\n\n";
1483
  my $errorMessage = "You provided invalid input to the script. " .
1484
                     "Try again please.";
1485
  fullTemplate( [], { stage => $templates->{'stage'},
1486
                      errorMessage => $errorMessage });
1487
  exit();
1488
}
1489

    
1490
#
1491
# perform a simple search against the LDAP database using 
1492
# a small subset of attributes of each dn and return it
1493
# as a table to the calling browser.
1494
#
1495
sub handleSimpleSearch {
1496

    
1497
    my $o = $query->param('o');
1498

    
1499
    my $ldapurl = $ldapConfig->{$o}{'url'};
1500
    my $searchBase = $ldapConfig->{$o}{'base'};
1501

    
1502
    print "Content-type: text/html\n\n";
1503

    
1504
    my $allParams = { 
1505
                      'cn' => $query->param('cn'),
1506
                      'sn' => $query->param('sn'),
1507
                      'gn' => $query->param('gn'),
1508
                      'o'  => $query->param('o'),
1509
                      'facsimiletelephonenumber' 
1510
                      => $query->param('facsimiletelephonenumber'),
1511
                      'mail' => $query->param('cmail'),
1512
                      'telephonenumber' => $query->param('telephonenumber'),
1513
                      'title' => $query->param('title'),
1514
                      'uid' => $query->param('uid'),
1515
                      'ou' => $query->param('ou'),
1516
                    };
1517

    
1518
    # Search LDAP for matching entries that already exist
1519
    my $filter = "(" . 
1520
                 $query->param('searchField') . "=" .
1521
                 "*" .
1522
                 $query->param('searchValue') .
1523
                 "*" .
1524
                 ")";
1525

    
1526
    my @attrs = [ 'sn', 
1527
                  'gn', 
1528
                  'cn', 
1529
                  'o', 
1530
                  'facsimiletelephonenumber', 
1531
                  'mail', 
1532
                  'telephoneNumber', 
1533
                  'title', 
1534
                  'uid', 
1535
                  'labeledURI', 
1536
                  'ou' ];
1537

    
1538
    my $found = searchDirectory($ldapurl, $searchBase, $filter, \@attrs);
1539

    
1540
    # Send back the search results
1541
    if ($found) {
1542
      fullTemplate( ('searchResults'), { stage => "searchresults",
1543
                                         allParams => $allParams,
1544
                                         foundAccounts => $found });
1545
    } else {
1546
      $found = "No entries matched your criteria.  Please try again\n";
1547

    
1548
      fullTemplate( ('searchResults'), { stage => "searchresults",
1549
                                         allParams => $allParams,
1550
                                         foundAccounts => $found });
1551
    }
1552

    
1553
    exit();
1554
}
1555

    
1556
#
1557
# search the LDAP directory to see if a similar account already exists
1558
#
1559
sub searchDirectory {
1560
    my $ldapurl = shift;
1561
    my $base = shift;
1562
    my $filter = shift;
1563
    my $attref = shift;
1564

    
1565
	my $mesg;
1566
    my $foundAccounts = 0;
1567
    
1568
    #if ldap server is down, a html file containing warning message will be returned
1569
    my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1570
    
1571
    if ($ldap) {
1572
    	$ldap->start_tls( verify => 'require',
1573
                      cafile => $ldapServerCACertFile);
1574
    	$ldap->bind( version => 3, anonymous => 1);
1575
    	my $mesg = $ldap->search (
1576
        	base   => $base,
1577
        	filter => $filter,
1578
        	attrs => @$attref,
1579
    	);
1580

    
1581
    	if ($mesg->count() > 0) {
1582
        	$foundAccounts = "";
1583
        	my $entry;
1584
        	foreach $entry ($mesg->sorted(['sn'])) {
1585
          		$foundAccounts .= "<tr>\n<td class=\"main\">\n";
1586
          		$foundAccounts .= "<a href=\"" unless 
1587
                    (!$entry->get_value('labeledURI'));
1588
         		 $foundAccounts .= $entry->get_value('labeledURI') unless
1589
                    (!$entry->get_value('labeledURI'));
1590
          		$foundAccounts .= "\">\n" unless 
1591
                    (!$entry->get_value('labeledURI'));
1592
          		$foundAccounts .= $entry->get_value('givenName');
1593
          		$foundAccounts .= "</a>\n" unless 
1594
                    (!$entry->get_value('labeledURI'));
1595
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1596
          		$foundAccounts .= "<a href=\"" unless 
1597
                    (!$entry->get_value('labeledURI'));
1598
          		$foundAccounts .= $entry->get_value('labeledURI') unless
1599
                    (!$entry->get_value('labeledURI'));
1600
          		$foundAccounts .= "\">\n" unless 
1601
                    (!$entry->get_value('labeledURI'));
1602
          		$foundAccounts .= $entry->get_value('sn');
1603
          		$foundAccounts .= "</a>\n";
1604
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1605
          		$foundAccounts .= $entry->get_value('mail');
1606
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1607
          		$foundAccounts .= $entry->get_value('telephonenumber');
1608
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1609
          		$foundAccounts .= $entry->get_value('title');
1610
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1611
          		$foundAccounts .= $entry->get_value('ou');
1612
          		$foundAccounts .= "\n</td>\n";
1613
          		$foundAccounts .= "</tr>\n";
1614
        	}
1615
    	}
1616
    	$ldap->unbind;   # take down session
1617
    }
1618
    return $foundAccounts;
1619
}
1620

    
1621
sub debug {
1622
    my $msg = shift;
1623
    
1624
    if ($debug) {
1625
        print STDERR "LDAPweb: $msg\n";
1626
    }
1627
}
1628

    
1629
sub handleLDAPBindFailure {
1630
    my $ldapAttemptUrl = shift;
1631
    my $primaryLdap =  $properties->getProperty('auth.url');
1632

    
1633
    if ($ldapAttemptUrl eq  $primaryLdap) {
1634
        handleGeneralServerFailure("The main LDAP server $ldapurl is down!");
1635
    } else {
1636
        debug("attempted to bind to nonresponsive LDAP server $ldapAttemptUrl, skipped.");
1637
    }
1638
}
1639

    
1640
sub handleGeneralServerFailure {
1641
    my $errorMessage = shift;
1642
    fullTemplate( ['mainServerFailure'], { errorMessage => $errorMessage });
1643
    exit(0);   
1644
   }
1645
    
1646
sub setVars {
1647
    my $paramVars = shift;
1648
    # initialize default parameters 
1649
    my $templateVars = { cfg => $cfg,
1650
                         styleSkinsPath => $contextUrl . "/style/skins",
1651
                         styleCommonPath => $contextUrl . "/style/common",
1652
                         contextUrl => $contextUrl,
1653
                         cgiPrefix => $cgiPrefix,
1654
                         orgList => \@validDisplayOrgList,
1655
                         config  => $config,
1656
    };
1657
    
1658
    # append customized params
1659
    while (my ($k, $v) = each (%$paramVars)) {
1660
        $templateVars->{$k} = $v;
1661
    }
1662
    
1663
    return $templateVars;
1664
} 
1665

    
1666
#Method to get the next avaliable uid number. We use the mechanism - http://www.rexconsulting.net/ldap-protocol-uidNumber.html
1667
sub getNextUidNumber {
1668

    
1669
    my $maxAttempt = $properties->getProperty('ldap.nextuid.maxattempt');
1670
    
1671
    my $ldapUsername = shift;
1672
    my $ldapPassword = shift;
1673
    
1674
    my $realUidNumber;
1675
    my $uidNumber;
1676
    my $entry;
1677
    my $mesg;
1678
    my $ldap;
1679
    
1680
    debug("ldap server: $ldapurl");
1681
    
1682
    #if main ldap server is down, a html file containing warning message will be returned
1683
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1684
    
1685
    if ($ldap) {
1686
    	my $existingHighUid=getExistingHighestUidNum($ldapUsername, $ldapPassword);
1687
        $ldap->start_tls( verify => 'require',
1688
                      cafile => $ldapServerCACertFile);
1689
        my $bindresult = $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword);
1690
        #read the uid value stored in uidObject class
1691
        for(my $index=0; $index<$maxAttempt; $index++) {
1692
            $mesg = $ldap->search(base  => $dn_store_next_uid, filter => '(objectClass=*)');
1693
            if ($mesg->count() > 0) {
1694
                debug("Find the cn - $dn_store_next_uid");
1695
                $entry = $mesg->pop_entry;
1696
                $uidNumber = $entry->get_value($attribute_name_store_next_uid);
1697
                if($uidNumber) {
1698
                    if (looks_like_number($uidNumber)) {
1699
                        debug("uid number is $uidNumber");
1700
                        #remove the uid attribute with the read value
1701
                        my $delMesg = $ldap->modify($dn_store_next_uid, delete => { $attribute_name_store_next_uid => $uidNumber});
1702
                        if($delMesg->is_error()) {
1703
                            my $error=$delMesg->error();
1704
                            my $errorName = $delMesg->error_name();
1705
                            debug("can't remove the attribute - $error");
1706
                            debug("can't remove the attribute and the error name - $errorName");
1707
                            #can't remove the attribute with the specified value - that means somebody modify the value in another route, so try it again
1708
                        } else {
1709
                            debug("Remove the attribute successfully and write a new increased value back");
1710
                            if($existingHighUid) {
1711
                            	debug("exiting high uid exists =======================================");
1712
                            	if($uidNumber <= $existingHighUid ) {
1713
                            		debug("The stored uidNumber $uidNumber is less than or equals the used uidNumber $existingHighUid, so we will use the new number which is $existingHighUid+1");
1714
                            		$uidNumber = $existingHighUid +1;
1715
                            	} 
1716
                            }                  
1717
                            my $newValue = $uidNumber +1;
1718
                            $delMesg = $ldap->modify($dn_store_next_uid, add => {$attribute_name_store_next_uid => $newValue});
1719
                            $realUidNumber = $uidNumber;
1720
                            last;
1721
                        }
1722
                    }
1723
                    
1724
               } else {
1725
                 debug("can't find the attribute - $attribute_name_store_next_uid in the $dn_store_next_uid and we will try again");
1726
               }
1727
            } 
1728
        }
1729
        $ldap->unbind;   # take down session
1730
    }
1731
    return $realUidNumber;
1732
}
1733

    
1734
#Method to get the existing high uidNumber in the account tree.
1735
sub getExistingHighestUidNum {
1736
    my $ldapUsername = shift;
1737
    my $ldapPassword = shift;
1738
   
1739
    my $high;
1740
    my $ldap;
1741
    my $storedUidNumber;
1742
    
1743
    
1744
    #if main ldap server is down, a html file containing warning message will be returned
1745
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
1746
    if ($ldap) {
1747
        $ldap->start_tls( verify => 'require',
1748
                      cafile => $ldapServerCACertFile);
1749
        my $bindresult = $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword);
1750
        my $mesg = $ldap->search(base  => $dn_store_next_uid, filter => '(objectClass=*)');
1751
         if ($mesg->count() > 0) {
1752
                debug("Find the cn - $dn_store_next_uid");
1753
                my  $entry = $mesg->pop_entry;
1754
                $storedUidNumber = $entry->get_value($attribute_name_store_next_uid);
1755
        }
1756
        my $authBase = $properties->getProperty("auth.base");
1757
        my $uids = $ldap->search(
1758
                        base => $authBase,
1759
                        scope => "sub",
1760
                        filter => "uidNumber=*", 
1761
                        attrs   => [ 'uidNumber' ],
1762
                        );
1763
       return unless $uids->count;
1764
  	    my @uids;
1765
        if ($uids->count > 0) {
1766
                foreach my $uid ($uids->all_entries) {
1767
                		if($storedUidNumber) {
1768
                			if( $uid->get_value('uidNumber') >= $storedUidNumber) {
1769
                				push @uids, $uid->get_value('uidNumber');
1770
                			}
1771
                		} else {
1772
                        	push @uids, $uid->get_value('uidNumber');
1773
                        }
1774
                }
1775
        }       
1776
        
1777
        if(@uids) {
1778
        	@uids = sort { $b <=> $a } @uids;
1779
        	$high = $uids[0];   
1780
        }    
1781
        debug("the highest exiting uidnumber is $high");
1782
        $ldap->unbind;   # take down session
1783
    }
1784
    return $high;
1785

    
1786
}
1787

    
1788

    
(10-10/14)