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