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