Project

General

Profile

1
#!/usr/bin/perl -w
2
#
3
#  '$RCSfile$'
4
#  Copyright: 2001 Regents of the University of California 
5
#
6
#   '$Author: walbridge $'
7
#     '$Date: 2009-03-25 18:29:54 -0700 (Wed, 25 Mar 2009) $'
8
# '$Revision: 4866 $' 
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 Cwd 'abs_path';
42

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

    
56
$properties->load(*METACAT_PROPERTIES);
57

    
58
# local directory configuration
59
my $skinsDir = "${workingDirectory}/../style/skins";
60
my $templatesDir = abs_path("${workingDirectory}/../style/common/templates");
61
my $tempDir = $properties->getProperty('application.tempDir');
62

    
63
# url configuration
64
my $server = $properties->splitToTree(qr/\./, 'server');
65
my $contextUrl = 'http://' . $properties->getProperty('server.name');
66
if ($properties->getProperty('server.httpPort') ne '80') {
67
        $contextUrl = $contextUrl . ':' . $properties->getProperty('server.httpPort');
68
}
69
$contextUrl = $contextUrl . '/' .  $properties->getProperty('application.context');
70

    
71
my $metacatUrl = $contextUrl . "/metacat";
72
my $cgiPrefix = "/" . $properties->getProperty('application.context') . "/cgi-bin";
73
my $styleSkinsPath = $contextUrl . "/style/skins";
74
my $styleCommonPath = $contextUrl . "/style/common";
75

    
76
my @errorMessages;
77
my $error = 0;
78

    
79
# Import all of the HTML form fields as variables
80
import_names('FORM');
81

    
82
# Must have a config to use Metacat
83
my $skinName = "";
84
if ($FORM::cfg) {
85
    $skinName = $FORM::cfg;
86
} elsif ($ARGV[0]) {
87
    $skinName = $ARGV[0];
88
} else {
89
    debug("No configuration set.");
90
    print "Content-type: text/html\n\n";
91
    print 'LDAPweb Error: The registry requires a skin name to continue.';
92
    exit();
93
}
94

    
95
# Metacat isn't initialized, the registry will fail in strange ways.
96
if (!($metacatUrl)) {
97
    debug("No Metacat.");
98
    print "Content-type: text/html\n\n";
99
    'Registry Error: Metacat is not initialized! Make sure' .
100
        ' MetacatUrl is set correctly in ' .  $skinName . '.cfg';
101
    exit();
102
}
103

    
104
my $skinProperties = new Config::Properties();
105
if (!($skinName)) {
106
    $error = "Application misconfigured.  Please contact the administrator.";
107
    push(@errorMessages, $error);
108
} else {
109
    my $skinProps = "$skinsDir/$skinName/$skinName.properties";
110
    unless (open (SKIN_PROPERTIES, $skinProps)) {
111
        print "Content-type: text/html\n\n";
112
        print "Unable to locate skin properties at $skinProps.  Is this path correct?";
113
        exit(0);
114
    }
115
    $skinProperties->load(*SKIN_PROPERTIES);
116
}
117

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

    
120
my $searchBase;
121
my $ldapUsername;
122
my $ldapPassword;
123
# TODO: when should we use surl instead? Is there a setting promoting one over the other?
124
# TODO: the default tree for accounts should be exposed somewhere, defaulting to unaffiliated
125
my $ldapurl = $properties->getProperty('auth.url');
126

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

    
130
# Get the CGI input variables
131
my $query = new CGI;
132
my $debug = 1;
133

    
134
#--------------------------------------------------------------------------80c->
135
# Set up the Template Toolkit to read html form templates
136

    
137
# templates hash, imported from ldap.templates tree in metacat.properties
138
my $templates = $properties->splitToTree(qr/\./, 'ldap.templates');
139
$$templates{'header'} = $skinProperties->getProperty("registry.templates.header");
140
$$templates{'footer'} = $skinProperties->getProperty("registry.templates.footer");
141

    
142
# set some configuration options for the template object
143
my $ttConfig = {
144
             INCLUDE_PATH => $templatesDir,
145
             INTERPOLATE  => 0,
146
             POST_CHOMP   => 1,
147
             DEBUG        => 1, 
148
             };
149

    
150
# create an instance of the template
151
my $template = Template->new($ttConfig) || handleGeneralServerFailure($Template::ERROR);
152

    
153
# custom LDAP properties hash
154
my $ldapCustom = $properties->splitToTree(qr/\./, 'ldap');
155

    
156
my $orgProps = $properties->splitToTree(qr/\./, 'organization');
157
my $orgNames = $properties->splitToTree(qr/\./, 'organization.name');
158
# pull out properties available e.g. 'name', 'base'
159
my @orgData = keys(%$orgProps);
160
my @orgList;
161
while (my ($oKey, $oVal) = each(%$orgNames)) {
162
    push(@orgList, $oKey);
163
}
164

    
165
my $authBase = $properties->getProperty("auth.base");
166
my $ldapConfig;
167
foreach my $o (@orgList) {
168
    foreach my $d (@orgData) {
169
        $ldapConfig->{$o}{$d} = $properties->getProperty("organization.$d.$o");
170
    }
171

    
172
    # set default base
173
    if (!$ldapConfig->{$o}{'base'}) {
174
        $ldapConfig->{$o}{'base'} = $authBase;
175
    }
176

    
177
    # include filter information. By default, our filters are 'o=$name', e.g. 'o=NAPIER'
178
    # these can be overridden by specifying them in metacat.properties. Non-default configs
179
    # such as UCNRS must specify all LDAP properties.
180
    if ($ldapConfig->{$o}{'base'} eq $authBase) {
181
        my $filter = "o=$o";
182
        if (!$ldapConfig->{$o}{'org'}) {
183
            $ldapConfig->{$o}{'org'} = $filter;
184
        }
185
        if (!$ldapConfig->{$o}{'filter'}) {
186
            $ldapConfig->{$o}{'filter'} = $filter;
187
        }
188
        # also include DN, which is just org + base
189
        if ($ldapConfig->{$o}{'org'}) {
190
            $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'org'} . "," . $ldapConfig->{$o}{'base'};
191
        }
192
    } else {
193
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'base'};
194
    }
195

    
196
    if (!$ldapConfig->{$o}{'user'}) {
197
        $ldapConfig->{$o}{'user'} = $ldapConfig->{'unaffiliated'}{'user'};
198
    }
199

    
200
    if (!$ldapConfig->{$o}{'password'}) {
201
        $ldapConfig->{$o}{'password'} = $ldapConfig->{'unaffiliated'}{'password'};
202
    }
203
}
204

    
205
#--------------------------------------------------------------------------80c->
206
# Define the main program logic that calls subroutines to do the work
207
#--------------------------------------------------------------------------80c->
208

    
209
# The processing step we are handling
210
my $stage = $query->param('stage') || $templates->{'stage'};
211

    
212
my $cfg = $query->param('cfg');
213
debug("started with stage $stage, cfg $cfg");
214

    
215
# define the possible stages
216
my %stages = (
217
              'initregister'      => \&handleInitRegister,
218
              'register'          => \&handleRegister,
219
              'registerconfirmed' => \&handleRegisterConfirmed,
220
              'simplesearch'      => \&handleSimpleSearch,
221
              'initaddentry'      => \&handleInitAddEntry,
222
              'addentry'          => \&handleAddEntry,
223
              'initmodifyentry'   => \&handleInitModifyEntry,
224
              'modifyentry'       => \&handleModifyEntry,
225
              'changepass'        => \&handleChangePassword,
226
              'initchangepass'    => \&handleInitialChangePassword,
227
              'resetpass'         => \&handleResetPassword,
228
              'initresetpass'     => \&handleInitialResetPassword,
229
             );
230

    
231
# call the appropriate routine based on the stage
232
if ( $stages{$stage} ) {
233
  $stages{$stage}->();
234
} else {
235
  &handleResponseMessage();
236
}
237

    
238
#--------------------------------------------------------------------------80c->
239
# Define the subroutines to do the work
240
#--------------------------------------------------------------------------80c->
241

    
242
sub fullTemplate {
243
    my $templateList = shift;
244
    my $templateVars = setVars(shift);
245

    
246
    $template->process( $templates->{'header'}, $templateVars );
247
    foreach my $tmpl (@{$templateList}) {
248
        $template->process( $templates->{$tmpl}, $templateVars );
249
    }
250
    $template->process( $templates->{'footer'}, $templateVars );
251
}
252

    
253
#
254
# create the initial registration form 
255
#
256
sub handleInitRegister {
257
  my $vars = shift;
258

    
259
  print "Content-type: text/html\n\n";
260
  # process the template files:
261
  fullTemplate(['register'], {stage => "register"}); 
262
  exit();
263
}
264

    
265
#
266
# process input from the register stage, which occurs when
267
# a user submits form data to create a new account
268
#
269
sub handleRegister {
270
    
271
    print "Content-type: text/html\n\n";
272

    
273
    my $allParams = { 'givenName' => $query->param('givenName'), 
274
                      'sn' => $query->param('sn'),
275
                      'o' => $query->param('o'), 
276
                      'mail' => $query->param('mail'), 
277
                      'uid' => $query->param('uid'), 
278
                      'userPassword' => $query->param('userPassword'), 
279
                      'userPassword2' => $query->param('userPassword2'), 
280
                      'title' => $query->param('title'), 
281
                      'telephoneNumber' => $query->param('telephoneNumber') };
282
    # Check that all required fields are provided and not null
283
    my @requiredParams = ( 'givenName', 'sn', 'o', 'mail', 
284
                           'uid', 'userPassword', 'userPassword2');
285
    if (! paramsAreValid(@requiredParams)) {
286
        my $errorMessage = "Required information is missing. " .
287
            "Please fill in all required fields and resubmit the form.";
288
        fullTemplate(['register'], { stage => "register",
289
                                     allParams => $allParams,
290
                                     errorMessage => $errorMessage });
291
        exit();
292
    } else {
293
        my $o = $query->param('o');    
294
        $searchBase = $ldapConfig->{$o}{'base'};  
295
    }
296

    
297
    # Search LDAP for matching entries that already exist
298
    # Some forms use a single text search box, whereas others search per
299
    # attribute.
300
    my $filter;
301
    if ($query->param('searchField')) {
302

    
303
      $filter = "(|" . 
304
                "(uid=" . $query->param('searchField') . ") " .
305
                "(mail=" . $query->param('searchField') . ")" .
306
                "(&(sn=" . $query->param('searchField') . ") " . 
307
                "(givenName=" . $query->param('searchField') . "))" . 
308
                ")";
309
    } else {
310
      $filter = "(|" . 
311
                "(uid=" . $query->param('uid') . ") " .
312
                "(mail=" . $query->param('mail') . ")" .
313
                "(&(sn=" . $query->param('sn') . ") " . 
314
                "(givenName=" . $query->param('givenName') . "))" . 
315
                ")";
316
    }
317

    
318
    my @attrs = [ 'uid', 'o', 'cn', 'mail', 'telephoneNumber', 'title' ];
319
    my $found = findExistingAccounts($ldapurl, $searchBase, $filter, \@attrs);
320

    
321
    # If entries match, send back a request to confirm new-user creation
322
    if ($found) {
323
      fullTemplate( ['registerMatch', 'register'], { stage => "registerconfirmed",
324
                                                     allParams => $allParams,
325
                                                     foundAccounts => $found });
326
    # Otherwise, create a new user in the LDAP directory
327
    } else {
328
        createAccount($allParams);
329
    }
330

    
331
    exit();
332
}
333

    
334
#
335
# process input from the registerconfirmed stage, which occurs when
336
# a user chooses to create an account despite similarities to other
337
# existing accounts
338
#
339
sub handleRegisterConfirmed {
340
  
341
    my $allParams = { 'givenName' => $query->param('givenName'), 
342
                      'sn' => $query->param('sn'),
343
                      'o' => 'unaffiliated', # only accept unaffiliated registration
344
                      'mail' => $query->param('mail'), 
345
                      'uid' => $query->param('uid'), 
346
                      'userPassword' => $query->param('userPassword'), 
347
                      'userPassword2' => $query->param('userPassword2'), 
348
                      'title' => $query->param('title'), 
349
                      'telephoneNumber' => $query->param('telephoneNumber') };
350
    print "Content-type: text/html\n\n";
351
    createAccount($allParams);
352
    exit();
353
}
354

    
355
#
356
# change a user's password upon request
357
#
358
sub handleChangePassword {
359

    
360
    print "Content-type: text/html\n\n";
361

    
362
    my $allParams = { 'test' => "1", };
363
    if ($query->param('uid')) {
364
        $$allParams{'uid'} = $query->param('uid');
365
    }
366
    if ($query->param('o')) {
367
        $$allParams{'o'} = $query->param('o');
368
        my $o = $query->param('o');
369
        
370
        $searchBase = $ldapConfig->{$o}{'base'};
371
    }
372

    
373

    
374
    # Check that all required fields are provided and not null
375
    my @requiredParams = ( 'uid', 'o', 'oldpass', 
376
                           'userPassword', 'userPassword2');
377
    if (! paramsAreValid(@requiredParams)) {
378
        my $errorMessage = "Required information is missing. " .
379
            "Please fill in all required fields and submit the form.";
380
        fullTemplate( ['changePass'], { stage => "changepass",
381
                                        allParams => $allParams,
382
                                        errorMessage => $errorMessage });
383
        exit();
384
    }
385

    
386
    # We have all of the info we need, so try to change the password
387
    if ($query->param('userPassword') =~ $query->param('userPassword2')) {
388

    
389
        my $o = $query->param('o');
390
        $searchBase = $ldapConfig->{$o}{'base'};
391
        $ldapUsername = $ldapConfig->{$o}{'user'};
392
        $ldapPassword = $ldapConfig->{$o}{'password'};
393

    
394
        my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};;
395
        if ($query->param('o') =~ "LTER") {
396
            fullTemplate( ['registerLter'] );
397
        } else {
398
            my $errorMessage = changePassword(
399
                    $dn, $query->param('userPassword'), 
400
                    $dn, $query->param('oldpass'), $query->param('o'));
401
            if ($errorMessage) {
402
                fullTemplate( ['changePass'], { stage => "changepass",
403
                                                allParams => $allParams,
404
                                                errorMessage => $errorMessage });
405
                exit();
406
            } else {
407
                fullTemplate( ['changePassSuccess'], { stage => "changepass",
408
                                                       allParams => $allParams });
409
                exit();
410
            }
411
        }
412
    } else {
413
        my $errorMessage = "The passwords do not match. Try again.";
414
        fullTemplate( ['changePass'], { stage => "changepass",
415
                                        allParams => $allParams,
416
                                        errorMessage => $errorMessage });
417
        exit();
418
    }
419
}
420

    
421
#
422
# change a user's password upon request - no input params
423
# only display chagepass template without any error
424
#
425
sub handleInitialChangePassword {
426
    print "Content-type: text/html\n\n";
427

    
428
    my $allParams = { 'test' => "1", };
429
    my $errorMessage = "";
430
    fullTemplate( ['changePass'], { stage => "changepass",
431
                                    errorMessage => $errorMessage });
432
    exit();
433
}
434

    
435
#
436
# reset a user's password upon request
437
#
438
sub handleResetPassword {
439

    
440
    print "Content-type: text/html\n\n";
441

    
442
    my $allParams = { 'test' => "1", };
443
    if ($query->param('uid')) {
444
        $$allParams{'uid'} = $query->param('uid');
445
    }
446
    if ($query->param('o')) {
447
        $$allParams{'o'} = $query->param('o');
448
        my $o = $query->param('o');
449
        
450
        $searchBase = $ldapConfig->{$o}{'base'};
451
        $ldapUsername = $ldapConfig->{$o}{'user'} . ',' . $searchBase;
452
        $ldapPassword = $ldapConfig->{$o}{'password'};
453
    }
454

    
455
    # Check that all required fields are provided and not null
456
    my @requiredParams = ( 'uid', 'o' );
457
    if (! paramsAreValid(@requiredParams)) {
458
        my $errorMessage = "Required information is missing. " .
459
            "Please fill in all required fields and submit the form.";
460
        fullTemplate( ['resetPass'],  { stage => "resetpass",
461
                                        allParams => $allParams,
462
                                        errorMessage => $errorMessage });
463
        exit();
464
    }
465

    
466
    # We have all of the info we need, so try to change the password
467
    my $o = $query->param('o');
468
    my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};
469
    debug("handleResetPassword: dn: $dn");
470
    if ($query->param('o') =~ "LTER") {
471
        fullTemplate( ['registerLter'] );
472
        exit();
473
    } else {
474
        my $errorMessage = "";
475
        my $recipient;
476
        my $userPass;
477
        my $entry = getLdapEntry($ldapurl, $searchBase, 
478
                $query->param('uid'), $query->param('o'));
479

    
480
        if ($entry) {
481
            $recipient = $entry->get_value('mail');
482
            $userPass = getRandomPassword();
483
            $errorMessage = changePassword($dn, $userPass, $ldapUsername, $ldapPassword, $query->param('o'));
484
        } else {
485
            $errorMessage = "User not found in database.  Please try again.";
486
        }
487

    
488
        if ($errorMessage) {
489
            fullTemplate( ['resetPass'], { stage => "resetpass",
490
                                           allParams => $allParams,
491
                                           errorMessage => $errorMessage });
492
            exit();
493
        } else {
494
            my $errorMessage = sendPasswordNotification($query->param('uid'),
495
                    $query->param('o'), $userPass, $recipient, $cfg);
496
            fullTemplate( ['resetPassSuccess'], { stage => "resetpass",
497
                                                  allParams => $allParams,
498
                                                  errorMessage => $errorMessage });
499
            exit();
500
        }
501
    }
502
}
503

    
504
#
505
# reset a user's password upon request- no initial params
506
# only display resetpass template without any error
507
#
508
sub handleInitialResetPassword {
509
    print "Content-type: text/html\n\n";
510
    my $errorMessage = "";
511
    fullTemplate( ['resetPass'], { stage => "resetpass",
512
                                   errorMessage => $errorMessage });
513
    exit();
514
}
515

    
516
#
517
# Construct a random string to use for a newly reset password
518
#
519
sub getRandomPassword {
520
    my $length = shift;
521
    if (!$length) {
522
        $length = 8;
523
    }
524
    my $newPass = "";
525

    
526
    my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ ^) );
527
    $newPass = join("", @chars[ map { rand @chars } ( 1 .. $length ) ]);
528
    return $newPass;
529
}
530

    
531
#
532
# Change a password to a new value, binding as the provided user
533
#
534
sub changePassword {
535
    my $userDN = shift;
536
    my $userPass = shift;
537
    my $bindDN = shift;
538
    my $bindPass = shift;
539
    my $o = shift;
540

    
541
    my $searchBase = $ldapConfig->{$o}{'base'};
542
    
543
    my $errorMessage = 0;
544
    my $ldap;
545
    
546
    #if main ldap server is down, a html file containing warning message will be returned
547
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
548
    
549
    if ($ldap) {
550
    	#$ldap->start_tls( verify => 'require',
551
                      #cafile => '/usr/share/ssl/ldapcerts/cacert.pem');
552
    	$ldap->start_tls( verify => 'none');
553
    	my $bindresult = $ldap->bind( version => 3, dn => $bindDN, 
554
                                  password => $bindPass );
555
    	if ($bindresult->code) {
556
        	$errorMessage = "Failed to log in. Are you sure your connection credentails are " .
557
                        "correct? Please correct and try again...";
558
       	 	return $errorMessage;
559
    	}
560

    
561
    	# Find the user here and change their entry
562
    	my $newpass = createSeededPassHash($userPass);
563
    	my $modifications = { userPassword => $newpass };
564
        debug("changePass: setting password for $userDN to $newpass");
565
    	my $result = $ldap->modify( $userDN, replace => { %$modifications });
566
    
567
    	if ($result->code()) {
568
            debug("changePass: error changing password: " . $result->error);
569
        	$errorMessage = "There was an error changing the password:" .
570
                           "<br />\n" . $result->error;
571
    	} 
572
    	$ldap->unbind;   # take down session
573
    }
574

    
575
    return $errorMessage;
576
}
577

    
578
#
579
# generate a Seeded SHA1 hash of a plaintext password
580
#
581
sub createSeededPassHash {
582
    my $secret = shift;
583

    
584
    my $salt = "";
585
    for (my $i=0; $i < 4; $i++) {
586
        $salt .= int(rand(10));
587
    }
588

    
589
    my $ctx = Digest::SHA1->new;
590
    $ctx->add($secret);
591
    $ctx->add($salt);
592
    my $hashedPasswd = '{SSHA}' . encode_base64($ctx->digest . $salt ,'');
593

    
594
    return $hashedPasswd;
595
}
596

    
597
#
598
# Look up an ldap entry for a user
599
#
600
sub getLdapEntry {
601
    my $ldapurl = shift;
602
    my $base = shift;
603
    my $username = shift;
604
    my $org = shift;
605

    
606
    my $entry = "";
607
    my $mesg;
608
    my $ldap;
609
    debug("ldap server: $ldapurl");
610

    
611
    #if main ldap server is down, a html file containing warning message will be returned
612
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
613
    
614
    if ($ldap) {
615
    	$ldap->start_tls( verify => 'none');
616
    	my $bindresult = $ldap->bind;
617
    	if ($bindresult->code) {
618
        	return $entry;
619
    	}
620

    
621
    	if($ldapConfig->{$org}{'filter'}){
622
            debug("getLdapEntry: filter set, searching for base=$base, " .
623
                  "(&(uid=$username)($ldapConfig->{$org}{'filter'})");
624
        	$mesg = $ldap->search ( base   => $base,
625
                filter => "(&(uid=$username)($ldapConfig->{$org}{'filter'}))");
626
    	} else {
627
            debug("getLdapEntry: no filter, searching for $base, (uid=$username)");
628
        	$mesg = $ldap->search ( base   => $base, filter => "(uid=$username)");
629
    	}
630
    
631
    	if ($mesg->count > 0) {
632
        	$entry = $mesg->pop_entry;
633
        	$ldap->unbind;   # take down session
634
    	} else {
635
        	$ldap->unbind;   # take down session
636
        	# Follow references by recursive call to self
637
        	my @references = $mesg->references();
638
        	for (my $i = 0; $i <= $#references; $i++) {
639
            	my $uri = URI->new($references[$i]);
640
            	my $host = $uri->host();
641
            	my $path = $uri->path();
642
            	$path =~ s/^\///;
643
            	$entry = &getLdapEntry($host, $path, $username, $org);
644
            	if ($entry) {
645
                    debug("getLdapEntry: recursion found $host, $path, $username, $org");
646
                	return $entry;
647
            	}
648
        	}
649
    	}
650
    }
651
    return $entry;
652
}
653

    
654
# 
655
# send an email message notifying the user of the pw change
656
#
657
sub sendPasswordNotification {
658
    my $username = shift;
659
    my $org = shift;
660
    my $newPass = shift;
661
    my $recipient = shift;
662
    my $cfg = shift;
663

    
664
    my $errorMessage = "";
665
    if ($recipient) {
666
        my $mailhost = $properties->getProperty('email.mailhost');
667
        my $sender =  $properties->getProperty('email.sender');
668
        # Send the email message to them
669
        my $smtp = Net::SMTP->new($mailhost);
670
        $smtp->mail($sender);
671
        $smtp->to($recipient);
672

    
673
        my $message = <<"        ENDOFMESSAGE";
674
        To: $recipient
675
        From: $sender
676
        Subject: KNB Password Reset
677
        
678
        Somebody (hopefully you) requested that your KNB password be reset.  
679
        This is generally done when somebody forgets their password.  Your 
680
        password can be changed by visiting the following URL:
681

    
682
        $contextUrl/cgi-bin/ldapweb.cgi?stage=changepass&cfg=$cfg
683

    
684
            Username: $username
685
        Organization: $org
686
        New Password: $newPass
687

    
688
        Thanks,
689
            The KNB Development Team
690
    
691
        ENDOFMESSAGE
692
        $message =~ s/^[ \t\r\f]+//gm;
693
    
694
        $smtp->data($message);
695
        $smtp->quit;
696
    } else {
697
        $errorMessage = "Failed to send password because I " .
698
                        "couldn't find a valid email address.";
699
    }
700
    return $errorMessage;
701
}
702

    
703
#
704
# search the LDAP directory to see if a similar account already exists
705
#
706
sub findExistingAccounts {
707
    my $ldapurl = shift;
708
    my $base = shift;
709
    my $filter = shift;
710
    my $attref = shift;
711
    my $ldap;
712
    my $mesg;
713

    
714
    my $foundAccounts = 0;
715

    
716
    #if main ldap server is down, a html file containing warning message will be returned
717
    debug("connecting to LDAP in findExistingAccounts with settings $ldapurl, $timeout");
718
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
719
    if ($ldap) {
720
    	$ldap->start_tls( verify => 'none');
721
    	$ldap->bind( version => 3, anonymous => 1);
722
		$mesg = $ldap->search (
723
			base   => $base,
724
			filter => $filter,
725
			attrs => @$attref,
726
		);
727

    
728
	    if ($mesg->count() > 0) {
729
			$foundAccounts = "";
730
			my $entry;
731
			foreach $entry ($mesg->all_entries) { 
732
				$foundAccounts .= "<p>\n<b><u>Account:</u> ";
733
				$foundAccounts .= $entry->dn();
734
				$foundAccounts .= "</b><br />\n";
735
				foreach my $attribute ($entry->attributes()) {
736
					$foundAccounts .= "$attribute: ";
737
					$foundAccounts .= $entry->get_value($attribute);
738
					$foundAccounts .= "<br />\n";
739
				}
740
				$foundAccounts .= "</p>\n";
741
			}
742
        }
743
    	$ldap->unbind;   # take down session
744

    
745
    	# Follow references
746
    	my @references = $mesg->references();
747
    	for (my $i = 0; $i <= $#references; $i++) {
748
        	my $uri = URI->new($references[$i]);
749
        	my $host = $uri->host();
750
        	my $path = $uri->path();
751
        	$path =~ s/^\///;
752
        	my $refFound = &findExistingAccounts($host, $path, $filter, $attref);
753
        	if ($refFound) {
754
            	$foundAccounts .= $refFound;
755
        	}
756
    	}
757
    }
758

    
759
    #print "<p>Checking referrals...</p>\n";
760
    #my @referrals = $mesg->referrals();
761
    #print "<p>Referrals count: ", scalar(@referrals), "</p>\n";
762
    #for (my $i = 0; $i <= $#referrals; $i++) {
763
        #print "<p>Referral: ", $referrals[$i], "</p>\n";
764
    #}
765

    
766
    return $foundAccounts;
767
}
768

    
769
#
770
# Validate that we have the proper set of input parameters
771
#
772
sub paramsAreValid {
773
    my @pnames = @_;
774

    
775
    my $allValid = 1;
776
    foreach my $parameter (@pnames) {
777
        if (!defined($query->param($parameter)) || 
778
            ! $query->param($parameter) ||
779
            $query->param($parameter) =~ /^\s+$/) {
780
            $allValid = 0;
781
        }
782
    }
783

    
784
    return $allValid;
785
}
786

    
787
#
788
# Bind to LDAP and create a new account using the information provided
789
# by the user
790
#
791
sub createAccount {
792
    my $allParams = shift;
793

    
794
    if ($query->param('o') =~ "LTER") {
795
        fullTemplate( ['registerLter'] );
796
    } else {
797

    
798
        # Be sure the passwords match
799
        if ($query->param('userPassword') !~ $query->param('userPassword2')) {
800
            my $errorMessage = "The passwords do not match. Try again.";
801
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
802
                                                            allParams => $allParams,
803
                                                            errorMessage => $errorMessage });
804
            exit();
805
        }
806

    
807
        my $o = $query->param('o');
808

    
809
        my $searchBase = $ldapConfig->{$o}{'base'};
810
        my $dnBase = $ldapConfig->{$o}{'dn'};
811
        my $ldapUsername = $ldapConfig->{$o}{'user'} . ',' . $searchBase;
812
        my $ldapPassword = $ldapConfig->{$o}{'password'};
813
        debug("LDAP connection to $ldapurl...");    
814
        #if main ldap server is down, a html file containing warning message will be returned
815
        my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
816
        
817
        if ($ldap) {
818
        	$ldap->start_tls( verify => 'none');
819
        	debug("Attempting to bind to LDAP server with dn = $ldapUsername, pwd = $ldapPassword");
820
        	$ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword );
821
        
822
        	my $dn = 'uid=' . $query->param('uid') . ',' . $dnBase;
823
        	debug("Inserting new entry for: $dn");
824

    
825
        	# Create a hashed version of the password
826
        	my $shapass = createSeededPassHash($query->param('userPassword'));
827

    
828
        	# Do the insertion
829
        	my $additions = [ 
830
                'uid'   => $query->param('uid'),
831
                'o'   => $query->param('o'),
832
                'cn'   => join(" ", $query->param('givenName'), 
833
                                    $query->param('sn')),
834
                'sn'   => $query->param('sn'),
835
                'givenName'   => $query->param('givenName'),
836
                'mail' => $query->param('mail'),
837
                'userPassword' => $shapass,
838
                'objectclass' => ['top', 'person', 'organizationalPerson', 
839
                                'inetOrgPerson', 'uidObject' ]
840
            	];
841
        	if (defined($query->param('telephoneNumber')) && 
842
            	$query->param('telephoneNumber') &&
843
            	! $query->param('telephoneNumber') =~ /^\s+$/) {
844
            	$$additions[$#$additions + 1] = 'telephoneNumber';
845
            	$$additions[$#$additions + 1] = $query->param('telephoneNumber');
846
        	}
847
        	if (defined($query->param('title')) && 
848
            	$query->param('title') &&
849
            	! $query->param('title') =~ /^\s+$/) {
850
            	$$additions[$#$additions + 1] = 'title';
851
            	$$additions[$#$additions + 1] = $query->param('title');
852
        	}
853
        	my $result = $ldap->add ( 'dn' => $dn, 'attr' => [ @$additions ]);
854
    
855
        	if ($result->code()) {
856
            	fullTemplate( ['registerFailed', 'register'], { stage => "register",
857
                                                            allParams => $allParams,
858
                                                            errorMessage => $result->error });
859
            	# TODO SCW was included as separate errors, test this
860
           	 	#$templateVars    = setVars({ stage => "register",
861
           	 	#                     allParams => $allParams });
862
            	#$template->process( $templates->{'register'}, $templateVars);
863
        	} else {
864
            	fullTemplate( ['success'] );
865
        	}
866

    
867
        	$ldap->unbind;   # take down session
868
        }
869
    }
870
}
871

    
872
sub handleResponseMessage {
873

    
874
  print "Content-type: text/html\n\n";
875
  my $errorMessage = "You provided invalid input to the script. " .
876
                     "Try again please.";
877
  fullTemplate( [], { stage => $templates->{'stage'},
878
                      errorMessage => $errorMessage });
879
  exit();
880
}
881

    
882
#
883
# perform a simple search against the LDAP database using 
884
# a small subset of attributes of each dn and return it
885
# as a table to the calling browser.
886
#
887
sub handleSimpleSearch {
888

    
889
    my $o = $query->param('o');
890

    
891
    my $ldapurl = $ldapConfig->{$o}{'url'};
892
    my $searchBase = $ldapConfig->{$o}{'base'};
893

    
894
    print "Content-type: text/html\n\n";
895

    
896
    my $allParams = { 
897
                      'cn' => $query->param('cn'),
898
                      'sn' => $query->param('sn'),
899
                      'gn' => $query->param('gn'),
900
                      'o'  => $query->param('o'),
901
                      'facsimiletelephonenumber' 
902
                      => $query->param('facsimiletelephonenumber'),
903
                      'mail' => $query->param('cmail'),
904
                      'telephonenumber' => $query->param('telephonenumber'),
905
                      'title' => $query->param('title'),
906
                      'uid' => $query->param('uid'),
907
                      'ou' => $query->param('ou'),
908
                    };
909

    
910
    # Search LDAP for matching entries that already exist
911
    my $filter = "(" . 
912
                 $query->param('searchField') . "=" .
913
                 "*" .
914
                 $query->param('searchValue') .
915
                 "*" .
916
                 ")";
917

    
918
    my @attrs = [ 'sn', 
919
                  'gn', 
920
                  'cn', 
921
                  'o', 
922
                  'facsimiletelephonenumber', 
923
                  'mail', 
924
                  'telephoneNumber', 
925
                  'title', 
926
                  'uid', 
927
                  'labeledURI', 
928
                  'ou' ];
929

    
930
    my $found = searchDirectory($ldapurl, $searchBase, $filter, \@attrs);
931

    
932
    # Send back the search results
933
    if ($found) {
934
      fullTemplate( ('searchResults'), { stage => "searchresults",
935
                                         allParams => $allParams,
936
                                         foundAccounts => $found });
937
    } else {
938
      $found = "No entries matched your criteria.  Please try again\n";
939

    
940
      fullTemplate( ('searchResults'), { stage => "searchresults",
941
                                         allParams => $allParams,
942
                                         foundAccounts => $found });
943
    }
944

    
945
    exit();
946
}
947

    
948
#
949
# search the LDAP directory to see if a similar account already exists
950
#
951
sub searchDirectory {
952
    my $ldapurl = shift;
953
    my $base = shift;
954
    my $filter = shift;
955
    my $attref = shift;
956

    
957
	my $mesg;
958
    my $foundAccounts = 0;
959
    
960
    #if ldap server is down, a html file containing warning message will be returned
961
    my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleLDAPBindFailure($ldapurl);
962
    
963
    if ($ldap) {
964
    	$ldap->start_tls( verify => 'none');
965
    	$ldap->bind( version => 3, anonymous => 1);
966
    	my $mesg = $ldap->search (
967
        	base   => $base,
968
        	filter => $filter,
969
        	attrs => @$attref,
970
    	);
971

    
972
    	if ($mesg->count() > 0) {
973
        	$foundAccounts = "";
974
        	my $entry;
975
        	foreach $entry ($mesg->sorted(['sn'])) {
976
          		$foundAccounts .= "<tr>\n<td class=\"main\">\n";
977
          		$foundAccounts .= "<a href=\"" unless 
978
                    (!$entry->get_value('labeledURI'));
979
         		 $foundAccounts .= $entry->get_value('labeledURI') unless
980
                    (!$entry->get_value('labeledURI'));
981
          		$foundAccounts .= "\">\n" unless 
982
                    (!$entry->get_value('labeledURI'));
983
          		$foundAccounts .= $entry->get_value('givenName');
984
          		$foundAccounts .= "</a>\n" unless 
985
                    (!$entry->get_value('labeledURI'));
986
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
987
          		$foundAccounts .= "<a href=\"" unless 
988
                    (!$entry->get_value('labeledURI'));
989
          		$foundAccounts .= $entry->get_value('labeledURI') unless
990
                    (!$entry->get_value('labeledURI'));
991
          		$foundAccounts .= "\">\n" unless 
992
                    (!$entry->get_value('labeledURI'));
993
          		$foundAccounts .= $entry->get_value('sn');
994
          		$foundAccounts .= "</a>\n";
995
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
996
          		$foundAccounts .= $entry->get_value('mail');
997
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
998
          		$foundAccounts .= $entry->get_value('telephonenumber');
999
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1000
          		$foundAccounts .= $entry->get_value('title');
1001
          		$foundAccounts .= "\n</td>\n<td class=\"main\">\n";
1002
          		$foundAccounts .= $entry->get_value('ou');
1003
          		$foundAccounts .= "\n</td>\n";
1004
          		$foundAccounts .= "</tr>\n";
1005
        	}
1006
    	}
1007
    	$ldap->unbind;   # take down session
1008
    }
1009
    return $foundAccounts;
1010
}
1011

    
1012
sub debug {
1013
    my $msg = shift;
1014
    
1015
    if ($debug) {
1016
        print STDERR "LDAPweb: $msg\n";
1017
    }
1018
}
1019

    
1020
sub handleLDAPBindFailure {
1021
    my $ldapAttemptUrl = shift;
1022
    my $primaryLdap =  $properties->getProperty('auth.url');
1023

    
1024
    if ($ldapAttemptUrl eq  $primaryLdap) {
1025
        handleGeneralServerFailure("The main LDAP server $ldapurl is down!");
1026
    } else {
1027
        debug("attempted to bind to nonresponsive LDAP server $ldapAttemptUrl, skipped.");
1028
    }
1029
}
1030

    
1031
sub handleGeneralServerFailure {
1032
    my $errorMessage = shift;
1033
    fullTemplate( ['mainServerFailure'], { errorMessage => $errorMessage });
1034
    exit(0);   
1035
   }
1036
    
1037
sub setVars {
1038
    my $paramVars = shift;
1039
    # initialize default parameters 
1040
    my $templateVars = { cfg => $cfg,
1041
                         styleSkinsPath => $contextUrl . "/style/skins",
1042
                         styleCommonPath => $contextUrl . "/style/common",
1043
                         contextUrl => $contextUrl,
1044
                         cgiPrefix => $cgiPrefix,
1045
                         orgList => \@orgList,
1046
                         config  => $config,
1047
    };
1048
    
1049
    # append customized params
1050
    while (my ($k, $v) = each (%$paramVars)) {
1051
        $templateVars->{$k} = $v;
1052
    }
1053
    
1054
    return $templateVars;
1055
} 
(10-10/14)