Project

General

Profile

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