Project

General

Profile

1 2341 sgarg
#!/usr/bin/perl -w
2
 #
3
 #  '$RCSfile$'
4
 #  Copyright: 2001 Regents of the University of California
5
 #
6
 #   '$Author$'
7
 #     '$Date$'
8
 # '$Revision$'
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 4394 walbridge
30
use lib '../WEB-INF/lib';
31 4080 daigle
use strict;             # turn on strict syntax checking
32
use Template;           # load the template-toolkit module
33 4394 walbridge
use CGI qw/:standard :html3/; # load the CGI module
34 4080 daigle
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 4394 walbridge
use Cwd 'abs_path';
42 2341 sgarg
43 4080 daigle
# Global configuration paramters
44 4394 walbridge
# This entire block (including skin parsing) could be pushed out to a separate .pm file
45 4080 daigle
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 4394 walbridge
    print "Content-type: text/html\n\n";
51 4080 daigle
    print "Unable to locate Metacat properties. Working directory is set as " .
52
        $workingDirectory .", is this correct?";
53
    exit(0);
54
}
55 2341 sgarg
56 4080 daigle
$properties->load(*METACAT_PROPERTIES);
57 4010 tao
58 4394 walbridge
# 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
                 $properties->getProperty('server.httpPort') . '/' .
67
                 $properties->getProperty('application.context');
68
69
my $metacatUrl = $contextUrl . "/metacat";
70
my $cgiPrefix = "/" . $properties->getProperty('application.context') . "/cgi-bin";
71
my $styleSkinsPath = $contextUrl . "/style/skins";
72
my $styleCommonPath = $contextUrl . "/style/common";
73
74
my @errorMessages;
75
my $error = 0;
76
77
# Import all of the HTML form fields as variables
78
import_names('FORM');
79
80
# Must have a config to use Metacat
81
my $skinName = "";
82
if ($FORM::cfg) {
83
    $skinName = $FORM::cfg;
84
} elsif ($ARGV[0]) {
85
    $skinName = $ARGV[0];
86
} else {
87 4747 walbridge
    debug("No configuration set.");
88 4394 walbridge
    print "Content-type: text/html\n\n";
89 4749 walbridge
    print 'LDAPweb Error: The registry requires a skin name to continue.';
90 4394 walbridge
    exit();
91
}
92
93
# Metacat isn't initialized, the registry will fail in strange ways.
94
if (!($metacatUrl)) {
95 4747 walbridge
    debug("No Metacat.");
96 4394 walbridge
    print "Content-type: text/html\n\n";
97
    'Registry Error: Metacat is not initialized! Make sure' .
98
        ' MetacatUrl is set correctly in ' .  $skinName . '.cfg';
99
    exit();
100
}
101
102
my $skinProperties = new Config::Properties();
103
if (!($skinName)) {
104
    $error = "Application misconfigured.  Please contact the administrator.";
105
    push(@errorMessages, $error);
106
} else {
107
    my $skinProps = "$skinsDir/$skinName/$skinName.properties";
108
    unless (open (SKIN_PROPERTIES, $skinProps)) {
109
        print "Content-type: text/html\n\n";
110
        print "Unable to locate skin properties at $skinProps.  Is this path correct?";
111
        exit(0);
112
    }
113
    $skinProperties->load(*SKIN_PROPERTIES);
114
}
115
116
my $config = $skinProperties->splitToTree(qr/\./, 'registry.config');
117
118
my $searchBase;
119
my $ldapUsername;
120
my $ldapPassword;
121 4728 walbridge
# TODO: when should we use surl instead? Is there a setting promoting one over the other?
122
# TODO: the default tree for accounts should be exposed somewhere, defaulting to unaffiliated
123
my $ldapurl = $properties->getProperty('auth.url');
124 4080 daigle
125
# Java uses miliseconds, Perl expects whole seconds
126 4728 walbridge
my $timeout = $properties->getProperty('ldap.connectTimeLimit') / 1000;
127
my $ldapdownmessage = "The main ldap server $ldapurl is down!";
128 4080 daigle
129 2341 sgarg
# Get the CGI input variables
130
my $query = new CGI;
131 4747 walbridge
my $debug = 1;
132 2341 sgarg
133
#--------------------------------------------------------------------------80c->
134
# Set up the Template Toolkit to read html form templates
135
136 4080 daigle
# templates hash, imported from ldap.templates tree in metacat.properties
137
my $templates = $properties->splitToTree(qr/\./, 'ldap.templates');
138 4394 walbridge
$$templates{'header'} = $skinProperties->getProperty("registry.templates.header");
139
$$templates{'footer'} = $skinProperties->getProperty("registry.templates.footer");
140 2341 sgarg
141
# set some configuration options for the template object
142 4394 walbridge
my $ttConfig = {
143
             INCLUDE_PATH => $templatesDir,
144
             INTERPOLATE  => 0,
145
             POST_CHOMP   => 1,
146
             DEBUG        => 1,
147 2341 sgarg
             };
148
149
# create an instance of the template
150 4394 walbridge
my $template = Template->new($ttConfig) || handleGeneralServerFailure($Template::ERROR);
151 2341 sgarg
152 4080 daigle
# custom LDAP properties hash
153
my $ldapCustom = $properties->splitToTree(qr/\./, 'ldap');
154 2341 sgarg
155 4394 walbridge
my $orgProps = $properties->splitToTree(qr/\./, 'organization');
156
my $orgNames = $properties->splitToTree(qr/\./, 'organization.name');
157
# pull out properties available e.g. 'name', 'base'
158
my @orgData = keys(%$orgProps);
159
my @orgList;
160
while (my ($oKey, $oVal) = each(%$orgNames)) {
161
    push(@orgList, $oKey);
162
}
163
164 4080 daigle
my $ldapConfig;
165
foreach my $o (@orgList) {
166 4394 walbridge
    foreach my $d (@orgData) {
167
        $ldapConfig->{$o}{$d} = $properties->getProperty("organization.$d.$o");
168 4080 daigle
    }
169 4394 walbridge
    # also include DN, which is just org + base
170
    if ($ldapConfig->{$o}{'org'}) {
171
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'org'} . "," . $ldapConfig->{$o}{'base'};
172
    } else {
173
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'base'};
174
    }
175 2341 sgarg
}
176
177
#--------------------------------------------------------------------------80c->
178
# Define the main program logic that calls subroutines to do the work
179
#--------------------------------------------------------------------------80c->
180
181
182
# The processing step we are handling
183 4080 daigle
my $stage = $query->param('stage') || $templates->{'stage'};
184 2341 sgarg
185
my $cfg = $query->param('cfg');
186
187
# define the possible stages
188
my %stages = (
189
              'initregister'      => \&handleInitRegister,
190
              'register'          => \&handleRegister,
191
              'registerconfirmed' => \&handleRegisterConfirmed,
192
              'simplesearch'      => \&handleSimpleSearch,
193
              'initaddentry'      => \&handleInitAddEntry,
194
              'addentry'          => \&handleAddEntry,
195
              'initmodifyentry'   => \&handleInitModifyEntry,
196
              'modifyentry'       => \&handleModifyEntry,
197 2972 jones
              'changepass'        => \&handleChangePassword,
198
              'initchangepass'    => \&handleInitialChangePassword,
199 2341 sgarg
              'resetpass'         => \&handleResetPassword,
200 2414 sgarg
              'initresetpass'     => \&handleInitialResetPassword,
201 2341 sgarg
             );
202 4394 walbridge
203 2341 sgarg
# call the appropriate routine based on the stage
204
if ( $stages{$stage} ) {
205
  $stages{$stage}->();
206
} else {
207
  &handleResponseMessage();
208
}
209
210
#--------------------------------------------------------------------------80c->
211
# Define the subroutines to do the work
212
#--------------------------------------------------------------------------80c->
213
214 4728 walbridge
sub fullTemplate {
215
    my $templateList = shift;
216
    my $templateVars = setVars(shift);
217 2341 sgarg
218 4728 walbridge
    $template->process( $templates->{'header'}, $templateVars );
219
    foreach my $tmpl (@{$templateList}) {
220
        $template->process( $templates->{$tmpl}, $templateVars );
221
    }
222
    $template->process( $templates->{'footer'}, $templateVars );
223
}
224
225 2341 sgarg
#
226
# create the initial registration form
227
#
228
sub handleInitRegister {
229
  my $vars = shift;
230
231
  print "Content-type: text/html\n\n";
232
  # process the template files:
233 4080 daigle
  fullTemplate(['register'], {stage => "register"});
234 2341 sgarg
  exit();
235
}
236
237
#
238
# process input from the register stage, which occurs when
239
# a user submits form data to create a new account
240
#
241
sub handleRegister {
242
243
    print "Content-type: text/html\n\n";
244
245
    my $allParams = { 'givenName' => $query->param('givenName'),
246
                      'sn' => $query->param('sn'),
247
                      'o' => $query->param('o'),
248
                      'mail' => $query->param('mail'),
249
                      'uid' => $query->param('uid'),
250
                      'userPassword' => $query->param('userPassword'),
251
                      'userPassword2' => $query->param('userPassword2'),
252
                      'title' => $query->param('title'),
253
                      'telephoneNumber' => $query->param('telephoneNumber') };
254
    # Check that all required fields are provided and not null
255
    my @requiredParams = ( 'givenName', 'sn', 'o', 'mail',
256
                           'uid', 'userPassword', 'userPassword2');
257
    if (! paramsAreValid(@requiredParams)) {
258
        my $errorMessage = "Required information is missing. " .
259
            "Please fill in all required fields and resubmit the form.";
260 4080 daigle
        fullTemplate(['register'], { stage => "register",
261
                                     allParams => $allParams,
262
                                     errorMessage => $errorMessage });
263
        exit();
264 2341 sgarg
    } else {
265 2972 jones
        my $o = $query->param('o');
266 4080 daigle
        $searchBase = $ldapConfig->{$o}{'base'};
267 2341 sgarg
    }
268
269
    # Search LDAP for matching entries that already exist
270
    # Some forms use a single text search box, whereas others search per
271
    # attribute.
272
    my $filter;
273
    if ($query->param('searchField')) {
274
275
      $filter = "(|" .
276
                "(uid=" . $query->param('searchField') . ") " .
277
                "(mail=" . $query->param('searchField') . ")" .
278
                "(&(sn=" . $query->param('searchField') . ") " .
279
                "(givenName=" . $query->param('searchField') . "))" .
280
                ")";
281
    } else {
282
      $filter = "(|" .
283
                "(uid=" . $query->param('uid') . ") " .
284
                "(mail=" . $query->param('mail') . ")" .
285
                "(&(sn=" . $query->param('sn') . ") " .
286
                "(givenName=" . $query->param('givenName') . "))" .
287
                ")";
288
    }
289
290
    my @attrs = [ 'uid', 'o', 'cn', 'mail', 'telephoneNumber', 'title' ];
291
    my $found = findExistingAccounts($ldapurl, $searchBase, $filter, \@attrs);
292
293
    # If entries match, send back a request to confirm new-user creation
294
    if ($found) {
295 4080 daigle
      fullTemplate( ['registerMatch', 'register'], { stage => "registerconfirmed",
296
                                                     allParams => $allParams,
297
                                                     foundAccounts => $found });
298 2341 sgarg
    # Otherwise, create a new user in the LDAP directory
299
    } else {
300
        createAccount($allParams);
301
    }
302
303
    exit();
304
}
305
306
#
307
# process input from the registerconfirmed stage, which occurs when
308
# a user chooses to create an account despite similarities to other
309
# existing accounts
310
#
311
sub handleRegisterConfirmed {
312
313
    my $allParams = { 'givenName' => $query->param('givenName'),
314
                      'sn' => $query->param('sn'),
315 4080 daigle
                      'o' => 'unaffiliated', # only accept unaffiliated registration
316 2341 sgarg
                      'mail' => $query->param('mail'),
317
                      'uid' => $query->param('uid'),
318
                      'userPassword' => $query->param('userPassword'),
319
                      'userPassword2' => $query->param('userPassword2'),
320
                      'title' => $query->param('title'),
321
                      'telephoneNumber' => $query->param('telephoneNumber') };
322
    print "Content-type: text/html\n\n";
323
    createAccount($allParams);
324
    exit();
325
}
326
327
#
328
# change a user's password upon request
329
#
330
sub handleChangePassword {
331
332
    print "Content-type: text/html\n\n";
333
334
    my $allParams = { 'test' => "1", };
335
    if ($query->param('uid')) {
336
        $$allParams{'uid'} = $query->param('uid');
337
    }
338
    if ($query->param('o')) {
339
        $$allParams{'o'} = $query->param('o');
340 2972 jones
        my $o = $query->param('o');
341
342 4080 daigle
        $searchBase = $ldapConfig->{$o}{'base'};
343 2341 sgarg
    }
344
345
346
    # Check that all required fields are provided and not null
347
    my @requiredParams = ( 'uid', 'o', 'oldpass',
348
                           'userPassword', 'userPassword2');
349
    if (! paramsAreValid(@requiredParams)) {
350
        my $errorMessage = "Required information is missing. " .
351
            "Please fill in all required fields and submit the form.";
352 4080 daigle
        fullTemplate( ['changePass'], { stage => "changepass",
353
                                        allParams => $allParams,
354
                                        errorMessage => $errorMessage });
355
        exit();
356 2341 sgarg
    }
357
358
    # We have all of the info we need, so try to change the password
359
    if ($query->param('userPassword') =~ $query->param('userPassword2')) {
360
361 2972 jones
        my $o = $query->param('o');
362 4080 daigle
        $searchBase = $ldapConfig->{$o}{'base'};
363
        $ldapUsername = $ldapConfig->{$o}{'user'};
364
        $ldapPassword = $ldapConfig->{$o}{'password'};
365 2341 sgarg
366 4080 daigle
        my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};;
367 2341 sgarg
        if ($query->param('o') =~ "LTER") {
368 4080 daigle
            fullTemplate( ['registerLter'] );
369 2341 sgarg
        } else {
370
            my $errorMessage = changePassword(
371
                    $dn, $query->param('userPassword'),
372
                    $dn, $query->param('oldpass'), $query->param('o'));
373 2972 jones
            if ($errorMessage) {
374 4080 daigle
                fullTemplate( ['changePass'], { stage => "changepass",
375
                                                allParams => $allParams,
376
                                                errorMessage => $errorMessage });
377
                exit();
378 2341 sgarg
            } else {
379 4080 daigle
                fullTemplate( ['changePassSuccess'], { stage => "changepass",
380
                                                       allParams => $allParams });
381
                exit();
382 2341 sgarg
            }
383
        }
384
    } else {
385
        my $errorMessage = "The passwords do not match. Try again.";
386 4080 daigle
        fullTemplate( ['changePass'], { stage => "changepass",
387
                                        allParams => $allParams,
388
                                        errorMessage => $errorMessage });
389
        exit();
390 2341 sgarg
    }
391
}
392
393
#
394 2414 sgarg
# change a user's password upon request - no input params
395
# only display chagepass template without any error
396
#
397
sub handleInitialChangePassword {
398
    print "Content-type: text/html\n\n";
399
400
    my $allParams = { 'test' => "1", };
401
    my $errorMessage = "";
402 4080 daigle
    fullTemplate( ['changePass'], { stage => "changepass",
403
                                    errorMessage => $errorMessage });
404
    exit();
405 2414 sgarg
}
406
407
#
408 2341 sgarg
# reset a user's password upon request
409
#
410
sub handleResetPassword {
411
412
    print "Content-type: text/html\n\n";
413
414
    my $allParams = { 'test' => "1", };
415
    if ($query->param('uid')) {
416
        $$allParams{'uid'} = $query->param('uid');
417
    }
418
    if ($query->param('o')) {
419
        $$allParams{'o'} = $query->param('o');
420 2972 jones
        my $o = $query->param('o');
421
422 4080 daigle
        $searchBase = $ldapConfig->{$o}{'base'};
423
        $ldapUsername = $ldapConfig->{$o}{'user'};
424
        $ldapPassword = $ldapConfig->{$o}{'password'};
425 2341 sgarg
    }
426
427
    # Check that all required fields are provided and not null
428
    my @requiredParams = ( 'uid', 'o' );
429
    if (! paramsAreValid(@requiredParams)) {
430
        my $errorMessage = "Required information is missing. " .
431
            "Please fill in all required fields and submit the form.";
432 4080 daigle
        fullTemplate( ['resetPass'],  { stage => "resetpass",
433
                                        allParams => $allParams,
434
                                        errorMessage => $errorMessage });
435
        exit();
436 2341 sgarg
    }
437
438
    # We have all of the info we need, so try to change the password
439
    my $o = $query->param('o');
440 4080 daigle
    my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};
441 2341 sgarg
    if ($query->param('o') =~ "LTER") {
442 4080 daigle
        fullTemplate( ['registerLter'] );
443
        exit();
444 2341 sgarg
    } else {
445
        my $errorMessage = "";
446
        my $recipient;
447
        my $userPass;
448
        my $entry = getLdapEntry($ldapurl, $searchBase,
449
                $query->param('uid'), $query->param('o'));
450
451
        if ($entry) {
452
            $recipient = $entry->get_value('mail');
453
            $userPass = getRandomPassword();
454 4080 daigle
            $errorMessage = changePassword($dn, $userPass, $ldapUsername, $ldapPassword, $query->param('o'));
455 2341 sgarg
        } else {
456
            $errorMessage = "User not found in database.  Please try again.";
457
        }
458
459
        if ($errorMessage) {
460 4080 daigle
            fullTemplate( ['resetPass'], { stage => "resetpass",
461
                                           allParams => $allParams,
462
                                           errorMessage => $errorMessage });
463
            exit();
464 2341 sgarg
        } else {
465
            my $errorMessage = sendPasswordNotification($query->param('uid'),
466 2972 jones
                    $query->param('o'), $userPass, $recipient, $cfg);
467 4080 daigle
            fullTemplate( ['resetPassSuccess'], { stage => "resetpass",
468
                                                  allParams => $allParams,
469
                                                  errorMessage => $errorMessage });
470
            exit();
471 2341 sgarg
        }
472
    }
473
}
474
475
#
476 2414 sgarg
# reset a user's password upon request- no initial params
477
# only display resetpass template without any error
478
#
479
sub handleInitialResetPassword {
480
    print "Content-type: text/html\n\n";
481
    my $errorMessage = "";
482 4080 daigle
    fullTemplate( ['resetPass'], { stage => "resetpass",
483
                                   errorMessage => $errorMessage });
484
    exit();
485 2414 sgarg
}
486
487
#
488 2341 sgarg
# Construct a random string to use for a newly reset password
489
#
490
sub getRandomPassword {
491
    my $length = shift;
492
    if (!$length) {
493
        $length = 8;
494
    }
495
    my $newPass = "";
496
497
    my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ ^) );
498
    $newPass = join("", @chars[ map { rand @chars } ( 1 .. $length ) ]);
499
    return $newPass;
500
}
501
502
#
503
# Change a password to a new value, binding as the provided user
504
#
505
sub changePassword {
506
    my $userDN = shift;
507
    my $userPass = shift;
508
    my $bindDN = shift;
509
    my $bindPass = shift;
510
    my $o = shift;
511
512 4080 daigle
    my $searchBase = $ldapConfig->{$o}{'base'};
513 2341 sgarg
514
    my $errorMessage = 0;
515 3177 tao
    my $ldap;
516 4394 walbridge
517
		#if main ldap server is down, a html file containing warning message will be returned
518
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
519
520
		#$ldap->start_tls( verify => 'require',
521 2972 jones
                      #cafile => '/usr/share/ssl/ldapcerts/cacert.pem');
522
    $ldap->start_tls( verify => 'none');
523 2341 sgarg
    my $bindresult = $ldap->bind( version => 3, dn => $bindDN,
524
                                  password => $bindPass );
525
    if ($bindresult->code) {
526
        $errorMessage = "Failed to log in. Are you sure your old " .
527
                        "password is correct? Try again...";
528
        return $errorMessage;
529
    }
530
531
    # Find the user here and change their entry
532
    my $newpass = createSeededPassHash($userPass);
533
    my $modifications = { userPassword => $newpass };
534
    my $result = $ldap->modify( $userDN, replace => { %$modifications });
535
536
    if ($result->code()) {
537
        my $errorMessage = "There was an error changing the password." .
538
                           "<br />\n" . $result->error;
539
    }
540
    $ldap->unbind;   # take down session
541
542
    return $errorMessage;
543
}
544
545
#
546
# generate a Seeded SHA1 hash of a plaintext password
547
#
548
sub createSeededPassHash {
549
    my $secret = shift;
550
551
    my $salt = "";
552
    for (my $i=0; $i < 4; $i++) {
553
        $salt .= int(rand(10));
554
    }
555
556
    my $ctx = Digest::SHA1->new;
557
    $ctx->add($secret);
558
    $ctx->add($salt);
559
    my $hashedPasswd = '{SSHA}' . encode_base64($ctx->digest . $salt ,'');
560
561
    return $hashedPasswd;
562
}
563
564
#
565
# Look up an ldap entry for a user
566
#
567
sub getLdapEntry {
568
    my $ldapurl = shift;
569
    my $base = shift;
570
    my $username = shift;
571
    my $org = shift;
572
573
    my $entry = "";
574
    my $mesg;
575 3177 tao
    my $ldap;
576 4749 walbridge
    debug("ldap server: $ldapurl");
577 4394 walbridge
578
    #if main ldap server is down, a html file containing warning message will be returned
579
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
580 2972 jones
    $ldap->start_tls( verify => 'none');
581 2341 sgarg
    my $bindresult = $ldap->bind;
582
    if ($bindresult->code) {
583
        return $entry;
584
    }
585
586 4080 daigle
    if($ldapConfig->{$org}{'filter'}){
587 2972 jones
        $mesg = $ldap->search ( base   => $base,
588 4080 daigle
                filter => "(&(uid=$username)($ldapConfig->{$org}{'filter'}))");
589 2341 sgarg
    } else {
590 2972 jones
        $mesg = $ldap->search ( base   => $base, filter => "(uid=$username)");
591 2341 sgarg
    }
592 3177 tao
593 2341 sgarg
    if ($mesg->count > 0) {
594
        $entry = $mesg->pop_entry;
595
        $ldap->unbind;   # take down session
596
    } else {
597
        $ldap->unbind;   # take down session
598
        # Follow references by recursive call to self
599
        my @references = $mesg->references();
600
        for (my $i = 0; $i <= $#references; $i++) {
601
            my $uri = URI->new($references[$i]);
602
            my $host = $uri->host();
603
            my $path = $uri->path();
604
            $path =~ s/^\///;
605
            $entry = &getLdapEntry($host, $path, $username, $org);
606
            if ($entry) {
607
                return $entry;
608
            }
609
        }
610
    }
611
    return $entry;
612
}
613
614
#
615
# send an email message notifying the user of the pw change
616
#
617
sub sendPasswordNotification {
618
    my $username = shift;
619
    my $org = shift;
620
    my $newPass = shift;
621
    my $recipient = shift;
622 2972 jones
    my $cfg = shift;
623 2341 sgarg
624
    my $errorMessage = "";
625
    if ($recipient) {
626 4394 walbridge
				my $mailhost = $properties->getProperty('email.mailhost');
627
				my $sender =  $properties->getProperty('email.sender');
628 2341 sgarg
        # Send the email message to them
629
        my $smtp = Net::SMTP->new($mailhost);
630
        $smtp->mail($sender);
631
        $smtp->to($recipient);
632
633
        my $message = <<"        ENDOFMESSAGE";
634
        To: $recipient
635
        From: $sender
636
        Subject: KNB Password Reset
637
638
        Somebody (hopefully you) requested that your KNB password be reset.
639
        This is generally done when somebody forgets their password.  Your
640
        password can be changed by visiting the following URL:
641
642 4080 daigle
        $cgiUrl?stage=changepass&cfg=$cfg
643 2341 sgarg
644
            Username: $username
645
        Organization: $org
646
        New Password: $newPass
647
648
        Thanks,
649
            The KNB Development Team
650
651
        ENDOFMESSAGE
652
        $message =~ s/^[ \t\r\f]+//gm;
653
654
        $smtp->data($message);
655
        $smtp->quit;
656
    } else {
657
        $errorMessage = "Failed to send password because I " .
658
                        "couldn't find a valid email address.";
659
    }
660
    return $errorMessage;
661
}
662
663
#
664
# search the LDAP directory to see if a similar account already exists
665
#
666
sub findExistingAccounts {
667
    my $ldapurl = shift;
668
    my $base = shift;
669
    my $filter = shift;
670
    my $attref = shift;
671 3175 tao
    my $ldap;
672 2341 sgarg
673
    my $foundAccounts = 0;
674 4749 walbridge
675 4394 walbridge
    #if main ldap server is down, a html file containing warning message will be returned
676
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
677 2972 jones
    $ldap->start_tls( verify => 'none');
678 2341 sgarg
    $ldap->bind( version => 3, anonymous => 1);
679
    my $mesg = $ldap->search (
680
        base   => $base,
681
        filter => $filter,
682
        attrs => @$attref,
683
    );
684
685
    if ($mesg->count() > 0) {
686
        $foundAccounts = "";
687
        my $entry;
688
        foreach $entry ($mesg->all_entries) {
689
            $foundAccounts .= "<p>\n<b><u>Account:</u> ";
690
            $foundAccounts .= $entry->dn();
691
            $foundAccounts .= "</b><br />\n";
692
            foreach my $attribute ($entry->attributes()) {
693
                $foundAccounts .= "$attribute: ";
694
                $foundAccounts .= $entry->get_value($attribute);
695
                $foundAccounts .= "<br />\n";
696
            }
697
            $foundAccounts .= "</p>\n";
698
        }
699
    }
700
    $ldap->unbind;   # take down session
701
702
    # Follow references
703
    my @references = $mesg->references();
704
    for (my $i = 0; $i <= $#references; $i++) {
705
        my $uri = URI->new($references[$i]);
706
        my $host = $uri->host();
707
        my $path = $uri->path();
708
        $path =~ s/^\///;
709
        my $refFound = &findExistingAccounts($host, $path, $filter, $attref);
710
        if ($refFound) {
711
            $foundAccounts .= $refFound;
712
        }
713
    }
714
715
    #print "<p>Checking referrals...</p>\n";
716
    #my @referrals = $mesg->referrals();
717
    #print "<p>Referrals count: ", scalar(@referrals), "</p>\n";
718
    #for (my $i = 0; $i <= $#referrals; $i++) {
719
        #print "<p>Referral: ", $referrals[$i], "</p>\n";
720
    #}
721
722
    return $foundAccounts;
723
}
724
725
#
726
# Validate that we have the proper set of input parameters
727
#
728
sub paramsAreValid {
729
    my @pnames = @_;
730
731
    my $allValid = 1;
732
    foreach my $parameter (@pnames) {
733
        if (!defined($query->param($parameter)) ||
734
            ! $query->param($parameter) ||
735
            $query->param($parameter) =~ /^\s+$/) {
736
            $allValid = 0;
737
        }
738
    }
739
740
    return $allValid;
741
}
742
743
#
744
# Bind to LDAP and create a new account using the information provided
745
# by the user
746
#
747
sub createAccount {
748
    my $allParams = shift;
749
750
    if ($query->param('o') =~ "LTER") {
751 4080 daigle
        fullTemplate( ['registerLter'] );
752 2341 sgarg
    } else {
753
754
        # Be sure the passwords match
755
        if ($query->param('userPassword') !~ $query->param('userPassword2')) {
756
            my $errorMessage = "The passwords do not match. Try again.";
757 4080 daigle
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
758
                                                            allParams => $allParams,
759
                                                            errorMessage => $errorMessage });
760
            exit();
761 2341 sgarg
        }
762
763 2972 jones
        my $o = $query->param('o');
764 2341 sgarg
765 4080 daigle
        my $searchBase = $ldapConfig->{$o}{'base'};
766
        my $dnBase = $ldapConfig->{$o}{'dn'};
767 4749 walbridge
        my $ldapUsername = $ldapConfig->{$o}{'user'} . ',' . $searchBase;
768
        my $ldapPassword = $ldapConfig->{$o}{'password'};
769 4748 walbridge
    		debug("LDAP connection to $ldapurl...");
770 3177 tao
        #if main ldap server is down, a html file containing warning message will be returned
771 4394 walbridge
        my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure("The ldap server " . $ldapurl . " is down!");
772 3177 tao
773
774 2972 jones
        $ldap->start_tls( verify => 'none');
775 4747 walbridge
				debug("Attempting to bind to LDAP server with dn = $ldapUsername, pwd = $ldapPassword");
776 4080 daigle
        $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword );
777 4749 walbridge
778 2341 sgarg
        my $dn = 'uid=' . $query->param('uid') . ',' . $dnBase;
779 4749 walbridge
				debug("Inserting new entry for: $dn");
780 2341 sgarg
781
        # Create a hashed version of the password
782
        my $shapass = createSeededPassHash($query->param('userPassword'));
783
784
        # Do the insertion
785
        my $additions = [
786
                'uid'   => $query->param('uid'),
787
                'o'   => $query->param('o'),
788
                'cn'   => join(" ", $query->param('givenName'),
789
                                    $query->param('sn')),
790
                'sn'   => $query->param('sn'),
791
                'givenName'   => $query->param('givenName'),
792
                'mail' => $query->param('mail'),
793
                'userPassword' => $shapass,
794
                'objectclass' => ['top', 'person', 'organizationalPerson',
795
                                'inetOrgPerson', 'uidObject' ]
796
            ];
797
        if (defined($query->param('telephoneNumber')) &&
798
            $query->param('telephoneNumber') &&
799
            ! $query->param('telephoneNumber') =~ /^\s+$/) {
800
            $$additions[$#$additions + 1] = 'telephoneNumber';
801
            $$additions[$#$additions + 1] = $query->param('telephoneNumber');
802
        }
803
        if (defined($query->param('title')) &&
804
            $query->param('title') &&
805
            ! $query->param('title') =~ /^\s+$/) {
806
            $$additions[$#$additions + 1] = 'title';
807
            $$additions[$#$additions + 1] = $query->param('title');
808
        }
809
        my $result = $ldap->add ( 'dn' => $dn, 'attr' => [ @$additions ]);
810
811
        if ($result->code()) {
812 4080 daigle
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
813
                                                            allParams => $allParams,
814
                                                            errorMessage => $result->error });
815
            # TODO SCW was included as separate errors, test this
816
            #$templateVars    = setVars({ stage => "register",
817
            #                     allParams => $allParams });
818
            #$template->process( $templates->{'register'}, $templateVars);
819 2341 sgarg
        } else {
820 4080 daigle
            fullTemplate( ['success'] );
821 2341 sgarg
        }
822
823
        $ldap->unbind;   # take down session
824
    }
825
}
826
827
sub handleResponseMessage {
828
829
  print "Content-type: text/html\n\n";
830
  my $errorMessage = "You provided invalid input to the script. " .
831
                     "Try again please.";
832 4080 daigle
  fullTemplate( [], { stage => $templates->{'stage'},
833
                      errorMessage => $errorMessage });
834
  exit();
835 2341 sgarg
}
836
837
#
838
# perform a simple search against the LDAP database using
839
# a small subset of attributes of each dn and return it
840
# as a table to the calling browser.
841
#
842
sub handleSimpleSearch {
843
844
    my $o = $query->param('o');
845
846 4080 daigle
    my $ldapurl = $ldapConfig->{$o}{'url'};
847
    my $searchBase = $ldapConfig->{$o}{'base'};
848 2341 sgarg
849
    print "Content-type: text/html\n\n";
850
851
    my $allParams = {
852
                      'cn' => $query->param('cn'),
853
                      'sn' => $query->param('sn'),
854
                      'gn' => $query->param('gn'),
855
                      'o'  => $query->param('o'),
856
                      'facsimiletelephonenumber'
857
                      => $query->param('facsimiletelephonenumber'),
858
                      'mail' => $query->param('cmail'),
859
                      'telephonenumber' => $query->param('telephonenumber'),
860
                      'title' => $query->param('title'),
861
                      'uid' => $query->param('uid'),
862
                      'ou' => $query->param('ou'),
863
                    };
864
865
    # Search LDAP for matching entries that already exist
866
    my $filter = "(" .
867
                 $query->param('searchField') . "=" .
868
                 "*" .
869
                 $query->param('searchValue') .
870
                 "*" .
871
                 ")";
872
873
    my @attrs = [ 'sn',
874
                  'gn',
875
                  'cn',
876
                  'o',
877
                  'facsimiletelephonenumber',
878
                  'mail',
879
                  'telephoneNumber',
880
                  'title',
881
                  'uid',
882
                  'labeledURI',
883
                  'ou' ];
884
885
    my $found = searchDirectory($ldapurl, $searchBase, $filter, \@attrs);
886
887
    # Send back the search results
888
    if ($found) {
889 4080 daigle
      fullTemplate( ('searchResults'), { stage => "searchresults",
890
                                         allParams => $allParams,
891
                                         foundAccounts => $found });
892 2341 sgarg
    } else {
893
      $found = "No entries matched your criteria.  Please try again\n";
894
895 4080 daigle
      fullTemplate( ('searchResults'), { stage => "searchresults",
896
                                         allParams => $allParams,
897
                                         foundAccounts => $found });
898 2341 sgarg
    }
899
900
    exit();
901
}
902
903
#
904
# search the LDAP directory to see if a similar account already exists
905
#
906
sub searchDirectory {
907
    my $ldapurl = shift;
908
    my $base = shift;
909
    my $filter = shift;
910
    my $attref = shift;
911
912
    my $foundAccounts = 0;
913 3177 tao
914
    #if ldap server is down, a html file containing warning message will be returned
915 4394 walbridge
    my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure("The ldap server " . $ldapurl . " is down!");
916 3177 tao
917 2972 jones
    $ldap->start_tls( verify => 'none');
918 2341 sgarg
    $ldap->bind( version => 3, anonymous => 1);
919
    my $mesg = $ldap->search (
920
        base   => $base,
921
        filter => $filter,
922
        attrs => @$attref,
923
    );
924
925
    if ($mesg->count() > 0) {
926
        $foundAccounts = "";
927
        my $entry;
928
        foreach $entry ($mesg->sorted(['sn'])) {
929
          $foundAccounts .= "<tr>\n<td class=\"main\">\n";
930
          $foundAccounts .= "<a href=\"" unless
931
                    (!$entry->get_value('labeledURI'));
932
          $foundAccounts .= $entry->get_value('labeledURI') unless
933
                    (!$entry->get_value('labeledURI'));
934
          $foundAccounts .= "\">\n" unless
935
                    (!$entry->get_value('labeledURI'));
936
          $foundAccounts .= $entry->get_value('givenName');
937
          $foundAccounts .= "</a>\n" unless
938
                    (!$entry->get_value('labeledURI'));
939
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
940
          $foundAccounts .= "<a href=\"" unless
941
                    (!$entry->get_value('labeledURI'));
942
          $foundAccounts .= $entry->get_value('labeledURI') unless
943
                    (!$entry->get_value('labeledURI'));
944
          $foundAccounts .= "\">\n" unless
945
                    (!$entry->get_value('labeledURI'));
946
          $foundAccounts .= $entry->get_value('sn');
947
          $foundAccounts .= "</a>\n";
948
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
949
          $foundAccounts .= $entry->get_value('mail');
950
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
951
          $foundAccounts .= $entry->get_value('telephonenumber');
952
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
953
          $foundAccounts .= $entry->get_value('title');
954
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
955
          $foundAccounts .= $entry->get_value('ou');
956
          $foundAccounts .= "\n</td>\n";
957
          $foundAccounts .= "</tr>\n";
958
        }
959
    }
960
    $ldap->unbind;   # take down session
961
    return $foundAccounts;
962
}
963
964
sub debug {
965
    my $msg = shift;
966
967
    if ($debug) {
968 4747 walbridge
        print STDERR "LDAPweb: $msg\n";
969 2341 sgarg
    }
970
}
971 3175 tao
972 3177 tao
sub handleGeneralServerFailure {
973
    my $errorMessage = shift;
974 4728 walbridge
    fullTemplate( ['mainServerFailure'], { errorMessage => $errorMessage });
975 3175 tao
    exit(0);
976
   }
977
978 4080 daigle
sub setVars {
979
    my $paramVars = shift;
980
    # initialize default parameters
981
    my $templateVars = { cfg => $cfg,
982 4394 walbridge
                         styleSkinsPath => $contextUrl . "/style/skins",
983
                         styleCommonPath => $contextUrl . "/style/common",
984
                         contextUrl => $contextUrl,
985 4080 daigle
                         orgList => \@orgList,
986 4394 walbridge
                         config  => $config,
987 4080 daigle
    };
988
989
    # append customized params
990
    while (my ($k, $v) = each (%$paramVars)) {
991
        $templateVars->{$k} = $v;
992
    }
993
994
    return $templateVars;
995
}