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