Project

General

Profile

1
#!/usr/bin/perl -w
2
 #
3
 #  '$RCSfile$'
4
 #  Copyright: 2001 Regents of the University of California 
5
 #
6
 #   '$Author: walbridge $'
7
 #     '$Date: 2009-01-06 10:17:28 -0800 (Tue, 06 Jan 2009) $'
8
 # '$Revision: 4728 $' 
9
 # 
10
 # This program is free software; you can redistribute it and/or modify
11
 # it under the terms of the GNU General Public License as published by
12
 # the Free Software Foundation; either version 2 of the License, or
13
 # (at your option) any later version.
14
 #
15
 # This program is distributed in the hope that it will be useful,
16
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 # GNU General Public License for more details.
19
 #
20
 # You should have received a copy of the GNU General Public License
21
 # along with this program; if not, write to the Free Software
22
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
 #
24

    
25
#
26
# This is a web-based application for allowing users to register a new
27
# account for Metacat access.  We currently only support LDAP even
28
# though metacat could potentially support other types of directories.
29

    
30
use lib '../WEB-INF/lib';
31
use strict;             # turn on strict syntax checking
32
use Template;           # load the template-toolkit module
33
use CGI qw/:standard :html3/; # load the CGI module 
34
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
use Cwd 'abs_path';
42

    
43
# Global configuration paramters
44
# This entire block (including skin parsing) could be pushed out to a separate .pm file
45
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
    print "Content-type: text/html\n\n";
51
    print "Unable to locate Metacat properties. Working directory is set as " . 
52
        $workingDirectory .", is this correct?";
53
    exit(0);
54
}
55

    
56
$properties->load(*METACAT_PROPERTIES);
57

    
58
# local directory configuration
59
my $skinsDir = "${workingDirectory}/../style/skins";
60
my $templatesDir = abs_path("${workingDirectory}/../style/common/templates");
61
my $tempDir = $properties->getProperty('application.tempDir');
62

    
63
# url configuration
64
my $server = $properties->splitToTree(qr/\./, 'server');
65
my $contextUrl = 'http://' . $properties->getProperty('server.name') . ':' .
66
                 $properties->getProperty('server.httpPort') . '/' .
67
                 $properties->getProperty('application.context');
68

    
69
my $metacatUrl = $contextUrl . "/metacat";
70
my $cgiPrefix = "/" . $properties->getProperty('application.context') . "/cgi-bin";
71
my $styleSkinsPath = $contextUrl . "/style/skins";
72
my $styleCommonPath = $contextUrl . "/style/common";
73

    
74
my @errorMessages;
75
my $error = 0;
76

    
77
# Import all of the HTML form fields as variables
78
import_names('FORM');
79

    
80
# Must have a config to use Metacat
81
my $skinName = "";
82
if ($FORM::cfg) {
83
    $skinName = $FORM::cfg;
84
} elsif ($ARGV[0]) {
85
    $skinName = $ARGV[0];
86
} else {
87
    debug("Registry: No configuration set.");
88
    print "Content-type: text/html\n\n";
89
    print 'Registry Error: The registry requires a skin name to continue.';
90
    exit();
91
}
92

    
93
# Metacat isn't initialized, the registry will fail in strange ways.
94
if (!($metacatUrl)) {
95
    debug("Registry: No Metacat.");
96
    print "Content-type: text/html\n\n";
97
    'Registry Error: Metacat is not initialized! Make sure' .
98
        ' MetacatUrl is set correctly in ' .  $skinName . '.cfg';
99
    exit();
100
}
101

    
102
my $skinProperties = new Config::Properties();
103
if (!($skinName)) {
104
    $error = "Application misconfigured.  Please contact the administrator.";
105
    push(@errorMessages, $error);
106
} else {
107
    my $skinProps = "$skinsDir/$skinName/$skinName.properties";
108
    unless (open (SKIN_PROPERTIES, $skinProps)) {
109
        print "Content-type: text/html\n\n";
110
        print "Unable to locate skin properties at $skinProps.  Is this path correct?";
111
        exit(0);
112
    }
113
    $skinProperties->load(*SKIN_PROPERTIES);
114
}
115

    
116
my $config = $skinProperties->splitToTree(qr/\./, 'registry.config');
117

    
118
my $searchBase;
119
my $ldapUsername;
120
my $ldapPassword;
121
# TODO: when should we use surl instead? Is there a setting promoting one over the other?
122
# TODO: the default tree for accounts should be exposed somewhere, defaulting to unaffiliated
123
my $ldapurl = $properties->getProperty('auth.url');
124

    
125
# Java uses miliseconds, Perl expects whole seconds
126
my $timeout = $properties->getProperty('ldap.connectTimeLimit') / 1000;
127
my $ldapdownmessage = "The main ldap server $ldapurl is down!";
128

    
129
# Get the CGI input variables
130
my $query = new CGI;
131
my $debug = 0;
132

    
133
#--------------------------------------------------------------------------80c->
134
# Set up the Template Toolkit to read html form templates
135

    
136
# templates hash, imported from ldap.templates tree in metacat.properties
137
my $templates = $properties->splitToTree(qr/\./, 'ldap.templates');
138
$$templates{'header'} = $skinProperties->getProperty("registry.templates.header");
139
$$templates{'footer'} = $skinProperties->getProperty("registry.templates.footer");
140

    
141
# set some configuration options for the template object
142
my $ttConfig = {
143
             INCLUDE_PATH => $templatesDir,
144
             INTERPOLATE  => 0,
145
             POST_CHOMP   => 1,
146
             DEBUG        => 1, 
147
             };
148

    
149
# create an instance of the template
150
my $template = Template->new($ttConfig) || handleGeneralServerFailure($Template::ERROR);
151

    
152
# custom LDAP properties hash
153
my $ldapCustom = $properties->splitToTree(qr/\./, 'ldap');
154

    
155
my $orgProps = $properties->splitToTree(qr/\./, 'organization');
156
my $orgNames = $properties->splitToTree(qr/\./, 'organization.name');
157
# pull out properties available e.g. 'name', 'base'
158
my @orgData = keys(%$orgProps);
159
my @orgList;
160
while (my ($oKey, $oVal) = each(%$orgNames)) {
161
    push(@orgList, $oKey);
162
}
163

    
164
my $ldapConfig;
165
foreach my $o (@orgList) {
166
    foreach my $d (@orgData) {
167
        $ldapConfig->{$o}{$d} = $properties->getProperty("organization.$d.$o");
168
    }
169
    # also include DN, which is just org + base
170
    if ($ldapConfig->{$o}{'org'}) {
171
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'org'} . "," . $ldapConfig->{$o}{'base'};
172
    } else {
173
        $ldapConfig->{$o}{'dn'} = $ldapConfig->{$o}{'base'};
174
    }
175
}
176

    
177
#--------------------------------------------------------------------------80c->
178
# Define the main program logic that calls subroutines to do the work
179
#--------------------------------------------------------------------------80c->
180

    
181

    
182
# The processing step we are handling
183
my $stage = $query->param('stage') || $templates->{'stage'};
184

    
185
my $cfg = $query->param('cfg');
186

    
187
# define the possible stages
188
my %stages = (
189
              'initregister'      => \&handleInitRegister,
190
              'register'          => \&handleRegister,
191
              'registerconfirmed' => \&handleRegisterConfirmed,
192
              'simplesearch'      => \&handleSimpleSearch,
193
              'initaddentry'      => \&handleInitAddEntry,
194
              'addentry'          => \&handleAddEntry,
195
              'initmodifyentry'   => \&handleInitModifyEntry,
196
              'modifyentry'       => \&handleModifyEntry,
197
              'changepass'        => \&handleChangePassword,
198
              'initchangepass'    => \&handleInitialChangePassword,
199
              'resetpass'         => \&handleResetPassword,
200
              'initresetpass'     => \&handleInitialResetPassword,
201
             );
202

    
203
# call the appropriate routine based on the stage
204
if ( $stages{$stage} ) {
205
  $stages{$stage}->();
206
} else {
207
  &handleResponseMessage();
208
}
209

    
210
#--------------------------------------------------------------------------80c->
211
# Define the subroutines to do the work
212
#--------------------------------------------------------------------------80c->
213

    
214
sub fullTemplate {
215
    my $templateList = shift;
216
    my $templateVars = setVars(shift);
217

    
218
    $template->process( $templates->{'header'}, $templateVars );
219
    foreach my $tmpl (@{$templateList}) {
220
        $template->process( $templates->{$tmpl}, $templateVars );
221
    }
222
    $template->process( $templates->{'footer'}, $templateVars );
223
}
224

    
225
#
226
# create the initial registration form 
227
#
228
sub handleInitRegister {
229
  my $vars = shift;
230

    
231
  print "Content-type: text/html\n\n";
232
  # process the template files:
233
  fullTemplate(['register'], {stage => "register"}); 
234
  exit();
235
}
236

    
237
#
238
# process input from the register stage, which occurs when
239
# a user submits form data to create a new account
240
#
241
sub handleRegister {
242
    
243
    print "Content-type: text/html\n\n";
244

    
245
    my $allParams = { 'givenName' => $query->param('givenName'), 
246
                      'sn' => $query->param('sn'),
247
                      'o' => $query->param('o'), 
248
                      'mail' => $query->param('mail'), 
249
                      'uid' => $query->param('uid'), 
250
                      'userPassword' => $query->param('userPassword'), 
251
                      'userPassword2' => $query->param('userPassword2'), 
252
                      'title' => $query->param('title'), 
253
                      'telephoneNumber' => $query->param('telephoneNumber') };
254
    # Check that all required fields are provided and not null
255
    my @requiredParams = ( 'givenName', 'sn', 'o', 'mail', 
256
                           'uid', 'userPassword', 'userPassword2');
257
    if (! paramsAreValid(@requiredParams)) {
258
        my $errorMessage = "Required information is missing. " .
259
            "Please fill in all required fields and resubmit the form.";
260
        fullTemplate(['register'], { stage => "register",
261
                                     allParams => $allParams,
262
                                     errorMessage => $errorMessage });
263
        exit();
264
    } else {
265
        my $o = $query->param('o');    
266
        $searchBase = $ldapConfig->{$o}{'base'};  
267
    }
268

    
269
    # Search LDAP for matching entries that already exist
270
    # Some forms use a single text search box, whereas others search per
271
    # attribute.
272
    my $filter;
273
    if ($query->param('searchField')) {
274

    
275
      $filter = "(|" . 
276
                "(uid=" . $query->param('searchField') . ") " .
277
                "(mail=" . $query->param('searchField') . ")" .
278
                "(&(sn=" . $query->param('searchField') . ") " . 
279
                "(givenName=" . $query->param('searchField') . "))" . 
280
                ")";
281
    } else {
282
      $filter = "(|" . 
283
                "(uid=" . $query->param('uid') . ") " .
284
                "(mail=" . $query->param('mail') . ")" .
285
                "(&(sn=" . $query->param('sn') . ") " . 
286
                "(givenName=" . $query->param('givenName') . "))" . 
287
                ")";
288
    }
289

    
290
    my @attrs = [ 'uid', 'o', 'cn', 'mail', 'telephoneNumber', 'title' ];
291
    my $found = findExistingAccounts($ldapurl, $searchBase, $filter, \@attrs);
292

    
293
    # If entries match, send back a request to confirm new-user creation
294
    if ($found) {
295
      fullTemplate( ['registerMatch', 'register'], { stage => "registerconfirmed",
296
                                                     allParams => $allParams,
297
                                                     foundAccounts => $found });
298
    # Otherwise, create a new user in the LDAP directory
299
    } else {
300
        #print("ingore create account\n");
301
        createAccount($allParams);
302
    }
303

    
304
    exit();
305
}
306

    
307
#
308
# process input from the registerconfirmed stage, which occurs when
309
# a user chooses to create an account despite similarities to other
310
# existing accounts
311
#
312
sub handleRegisterConfirmed {
313
  
314
    my $allParams = { 'givenName' => $query->param('givenName'), 
315
                      'sn' => $query->param('sn'),
316
                      'o' => 'unaffiliated', # only accept unaffiliated registration
317
                      'mail' => $query->param('mail'), 
318
                      'uid' => $query->param('uid'), 
319
                      'userPassword' => $query->param('userPassword'), 
320
                      'userPassword2' => $query->param('userPassword2'), 
321
                      'title' => $query->param('title'), 
322
                      'telephoneNumber' => $query->param('telephoneNumber') };
323
    print "Content-type: text/html\n\n";
324
    createAccount($allParams);
325
    exit();
326
}
327

    
328
#
329
# change a user's password upon request
330
#
331
sub handleChangePassword {
332

    
333
    print "Content-type: text/html\n\n";
334

    
335
    my $allParams = { 'test' => "1", };
336
    if ($query->param('uid')) {
337
        $$allParams{'uid'} = $query->param('uid');
338
    }
339
    if ($query->param('o')) {
340
        $$allParams{'o'} = $query->param('o');
341
        my $o = $query->param('o');
342
        
343
        $searchBase = $ldapConfig->{$o}{'base'};
344
    }
345

    
346

    
347
    # Check that all required fields are provided and not null
348
    my @requiredParams = ( 'uid', 'o', 'oldpass', 
349
                           'userPassword', 'userPassword2');
350
    if (! paramsAreValid(@requiredParams)) {
351
        my $errorMessage = "Required information is missing. " .
352
            "Please fill in all required fields and submit the form.";
353
        fullTemplate( ['changePass'], { stage => "changepass",
354
                                        allParams => $allParams,
355
                                        errorMessage => $errorMessage });
356
        exit();
357
    }
358

    
359
    # We have all of the info we need, so try to change the password
360
    if ($query->param('userPassword') =~ $query->param('userPassword2')) {
361

    
362
        my $o = $query->param('o');
363
        $searchBase = $ldapConfig->{$o}{'base'};
364
        $ldapUsername = $ldapConfig->{$o}{'user'};
365
        $ldapPassword = $ldapConfig->{$o}{'password'};
366

    
367
        my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};;
368
        if ($query->param('o') =~ "LTER") {
369
            fullTemplate( ['registerLter'] );
370
        } else {
371
            my $errorMessage = changePassword(
372
                    $dn, $query->param('userPassword'), 
373
                    $dn, $query->param('oldpass'), $query->param('o'));
374
            if ($errorMessage) {
375
                fullTemplate( ['changePass'], { stage => "changepass",
376
                                                allParams => $allParams,
377
                                                errorMessage => $errorMessage });
378
                exit();
379
            } else {
380
                fullTemplate( ['changePassSuccess'], { stage => "changepass",
381
                                                       allParams => $allParams });
382
                exit();
383
            }
384
        }
385
    } else {
386
        my $errorMessage = "The passwords do not match. Try again.";
387
        fullTemplate( ['changePass'], { stage => "changepass",
388
                                        allParams => $allParams,
389
                                        errorMessage => $errorMessage });
390
        exit();
391
    }
392
}
393

    
394
#
395
# change a user's password upon request - no input params
396
# only display chagepass template without any error
397
#
398
sub handleInitialChangePassword {
399
    print "Content-type: text/html\n\n";
400

    
401
    my $allParams = { 'test' => "1", };
402
    my $errorMessage = "";
403
    fullTemplate( ['changePass'], { stage => "changepass",
404
                                    errorMessage => $errorMessage });
405
    exit();
406
}
407

    
408
#
409
# reset a user's password upon request
410
#
411
sub handleResetPassword {
412

    
413
    print "Content-type: text/html\n\n";
414

    
415
    my $allParams = { 'test' => "1", };
416
    if ($query->param('uid')) {
417
        $$allParams{'uid'} = $query->param('uid');
418
    }
419
    if ($query->param('o')) {
420
        $$allParams{'o'} = $query->param('o');
421
        my $o = $query->param('o');
422
        
423
        $searchBase = $ldapConfig->{$o}{'base'};
424
        $ldapUsername = $ldapConfig->{$o}{'user'};
425
        $ldapPassword = $ldapConfig->{$o}{'password'};
426
    }
427

    
428
    # Check that all required fields are provided and not null
429
    my @requiredParams = ( 'uid', 'o' );
430
    if (! paramsAreValid(@requiredParams)) {
431
        my $errorMessage = "Required information is missing. " .
432
            "Please fill in all required fields and submit the form.";
433
        fullTemplate( ['resetPass'],  { stage => "resetpass",
434
                                        allParams => $allParams,
435
                                        errorMessage => $errorMessage });
436
        exit();
437
    }
438

    
439
    # We have all of the info we need, so try to change the password
440
    my $o = $query->param('o');
441
    my $dn = "uid=" . $query->param('uid') . "," . $ldapConfig->{$o}{'dn'};
442
    if ($query->param('o') =~ "LTER") {
443
        fullTemplate( ['registerLter'] );
444
        exit();
445
    } else {
446
        my $errorMessage = "";
447
        my $recipient;
448
        my $userPass;
449
        my $entry = getLdapEntry($ldapurl, $searchBase, 
450
                $query->param('uid'), $query->param('o'));
451

    
452
        if ($entry) {
453
            $recipient = $entry->get_value('mail');
454
            $userPass = getRandomPassword();
455
            $errorMessage = changePassword($dn, $userPass, $ldapUsername, $ldapPassword, $query->param('o'));
456
        } else {
457
            $errorMessage = "User not found in database.  Please try again.";
458
        }
459

    
460
        if ($errorMessage) {
461
            fullTemplate( ['resetPass'], { stage => "resetpass",
462
                                           allParams => $allParams,
463
                                           errorMessage => $errorMessage });
464
            exit();
465
        } else {
466
            my $errorMessage = sendPasswordNotification($query->param('uid'),
467
                    $query->param('o'), $userPass, $recipient, $cfg);
468
            fullTemplate( ['resetPassSuccess'], { stage => "resetpass",
469
                                                  allParams => $allParams,
470
                                                  errorMessage => $errorMessage });
471
            exit();
472
        }
473
    }
474
}
475

    
476
#
477
# reset a user's password upon request- no initial params
478
# only display resetpass template without any error
479
#
480
sub handleInitialResetPassword {
481
    print "Content-type: text/html\n\n";
482
    my $errorMessage = "";
483
    fullTemplate( ['resetPass'], { stage => "resetpass",
484
                                   errorMessage => $errorMessage });
485
    exit();
486
}
487

    
488
#
489
# Construct a random string to use for a newly reset password
490
#
491
sub getRandomPassword {
492
    my $length = shift;
493
    if (!$length) {
494
        $length = 8;
495
    }
496
    my $newPass = "";
497

    
498
    my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ ^) );
499
    $newPass = join("", @chars[ map { rand @chars } ( 1 .. $length ) ]);
500
    return $newPass;
501
}
502

    
503
#
504
# Change a password to a new value, binding as the provided user
505
#
506
sub changePassword {
507
    my $userDN = shift;
508
    my $userPass = shift;
509
    my $bindDN = shift;
510
    my $bindPass = shift;
511
    my $o = shift;
512

    
513
    my $searchBase = $ldapConfig->{$o}{'base'};
514
    
515
    my $errorMessage = 0;
516
    my $ldap;
517
    
518
		#if main ldap server is down, a html file containing warning message will be returned
519
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
520
    
521
		#$ldap->start_tls( verify => 'require',
522
                      #cafile => '/usr/share/ssl/ldapcerts/cacert.pem');
523
    $ldap->start_tls( verify => 'none');
524
    my $bindresult = $ldap->bind( version => 3, dn => $bindDN, 
525
                                  password => $bindPass );
526
    if ($bindresult->code) {
527
        $errorMessage = "Failed to log in. Are you sure your old " .
528
                        "password is correct? Try again...";
529
        return $errorMessage;
530
    }
531

    
532
    # Find the user here and change their entry
533
    my $newpass = createSeededPassHash($userPass);
534
    my $modifications = { userPassword => $newpass };
535
    my $result = $ldap->modify( $userDN, replace => { %$modifications });
536
    
537
    if ($result->code()) {
538
        my $errorMessage = "There was an error changing the password." .
539
                           "<br />\n" . $result->error;
540
    } 
541
    $ldap->unbind;   # take down session
542

    
543
    return $errorMessage;
544
}
545

    
546
#
547
# generate a Seeded SHA1 hash of a plaintext password
548
#
549
sub createSeededPassHash {
550
    my $secret = shift;
551

    
552
    my $salt = "";
553
    for (my $i=0; $i < 4; $i++) {
554
        $salt .= int(rand(10));
555
    }
556

    
557
    my $ctx = Digest::SHA1->new;
558
    $ctx->add($secret);
559
    $ctx->add($salt);
560
    my $hashedPasswd = '{SSHA}' . encode_base64($ctx->digest . $salt ,'');
561

    
562
    return $hashedPasswd;
563
}
564

    
565
#
566
# Look up an ldap entry for a user
567
#
568
sub getLdapEntry {
569
    my $ldapurl = shift;
570
    my $base = shift;
571
    my $username = shift;
572
    my $org = shift;
573

    
574
    my $entry = "";
575
    my $mesg;
576
    my $ldap;
577
    print("ldap server ", $ldapurl, "\n");
578

    
579
    #if main ldap server is down, a html file containing warning message will be returned
580
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
581
    $ldap->start_tls( verify => 'none');
582
    my $bindresult = $ldap->bind;
583
    if ($bindresult->code) {
584
        return $entry;
585
    }
586

    
587
    if($ldapConfig->{$org}{'filter'}){
588
        $mesg = $ldap->search ( base   => $base,
589
                filter => "(&(uid=$username)($ldapConfig->{$org}{'filter'}))");
590
    } else {
591
        $mesg = $ldap->search ( base   => $base, filter => "(uid=$username)");
592
    }
593
    
594
    if ($mesg->count > 0) {
595
        $entry = $mesg->pop_entry;
596
        $ldap->unbind;   # take down session
597
    } else {
598
        $ldap->unbind;   # take down session
599
        # Follow references by recursive call to self
600
        my @references = $mesg->references();
601
        for (my $i = 0; $i <= $#references; $i++) {
602
            my $uri = URI->new($references[$i]);
603
            my $host = $uri->host();
604
            my $path = $uri->path();
605
            $path =~ s/^\///;
606
            $entry = &getLdapEntry($host, $path, $username, $org);
607
            if ($entry) {
608
                return $entry;
609
            }
610
        }
611
    }
612
    return $entry;
613
}
614

    
615
# 
616
# send an email message notifying the user of the pw change
617
#
618
sub sendPasswordNotification {
619
    my $username = shift;
620
    my $org = shift;
621
    my $newPass = shift;
622
    my $recipient = shift;
623
    my $cfg = shift;
624

    
625
    my $errorMessage = "";
626
    if ($recipient) {
627
				my $mailhost = $properties->getProperty('email.mailhost');
628
				my $sender =  $properties->getProperty('email.sender');
629
        # Send the email message to them
630
        my $smtp = Net::SMTP->new($mailhost);
631
        $smtp->mail($sender);
632
        $smtp->to($recipient);
633

    
634
        my $message = <<"        ENDOFMESSAGE";
635
        To: $recipient
636
        From: $sender
637
        Subject: KNB Password Reset
638
        
639
        Somebody (hopefully you) requested that your KNB password be reset.  
640
        This is generally done when somebody forgets their password.  Your 
641
        password can be changed by visiting the following URL:
642

    
643
        $cgiUrl?stage=changepass&cfg=$cfg
644

    
645
            Username: $username
646
        Organization: $org
647
        New Password: $newPass
648

    
649
        Thanks,
650
            The KNB Development Team
651
    
652
        ENDOFMESSAGE
653
        $message =~ s/^[ \t\r\f]+//gm;
654
    
655
        $smtp->data($message);
656
        $smtp->quit;
657
    } else {
658
        $errorMessage = "Failed to send password because I " .
659
                        "couldn't find a valid email address.";
660
    }
661
    return $errorMessage;
662
}
663

    
664
#
665
# search the LDAP directory to see if a similar account already exists
666
#
667
sub findExistingAccounts {
668
    my $ldapurl = shift;
669
    my $base = shift;
670
    my $filter = shift;
671
    my $attref = shift;
672
    my $ldap;
673

    
674
    my $foundAccounts = 0;
675
    #print("the ldapurl in findExstingAccounts is ", $ldapurl, "\n");
676
    #if main ldap server is down, a html file containing warning message will be returned
677
    $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure($ldapdownmessage);
678
    $ldap->start_tls( verify => 'none');
679
    $ldap->bind( version => 3, anonymous => 1);
680
    my $mesg = $ldap->search (
681
        base   => $base,
682
        filter => $filter,
683
        attrs => @$attref,
684
    );
685

    
686
    if ($mesg->count() > 0) {
687
        $foundAccounts = "";
688
        my $entry;
689
        foreach $entry ($mesg->all_entries) { 
690
            $foundAccounts .= "<p>\n<b><u>Account:</u> ";
691
            $foundAccounts .= $entry->dn();
692
            $foundAccounts .= "</b><br />\n";
693
            foreach my $attribute ($entry->attributes()) {
694
                $foundAccounts .= "$attribute: ";
695
                $foundAccounts .= $entry->get_value($attribute);
696
                $foundAccounts .= "<br />\n";
697
            }
698
            $foundAccounts .= "</p>\n";
699
        }
700
    }
701
    $ldap->unbind;   # take down session
702

    
703
    # Follow references
704
    my @references = $mesg->references();
705
    for (my $i = 0; $i <= $#references; $i++) {
706
        my $uri = URI->new($references[$i]);
707
        my $host = $uri->host();
708
        my $path = $uri->path();
709
        $path =~ s/^\///;
710
        my $refFound = &findExistingAccounts($host, $path, $filter, $attref);
711
        if ($refFound) {
712
            $foundAccounts .= $refFound;
713
        }
714
    }
715

    
716
    #print "<p>Checking referrals...</p>\n";
717
    #my @referrals = $mesg->referrals();
718
    #print "<p>Referrals count: ", scalar(@referrals), "</p>\n";
719
    #for (my $i = 0; $i <= $#referrals; $i++) {
720
        #print "<p>Referral: ", $referrals[$i], "</p>\n";
721
    #}
722

    
723
    return $foundAccounts;
724
}
725

    
726
#
727
# Validate that we have the proper set of input parameters
728
#
729
sub paramsAreValid {
730
    my @pnames = @_;
731

    
732
    my $allValid = 1;
733
    foreach my $parameter (@pnames) {
734
        if (!defined($query->param($parameter)) || 
735
            ! $query->param($parameter) ||
736
            $query->param($parameter) =~ /^\s+$/) {
737
            $allValid = 0;
738
        }
739
    }
740

    
741
    return $allValid;
742
}
743

    
744
#
745
# Bind to LDAP and create a new account using the information provided
746
# by the user
747
#
748
sub createAccount {
749
    my $allParams = shift;
750

    
751
    if ($query->param('o') =~ "LTER") {
752
        fullTemplate( ['registerLter'] );
753
    } else {
754

    
755
        # Be sure the passwords match
756
        if ($query->param('userPassword') !~ $query->param('userPassword2')) {
757
            my $errorMessage = "The passwords do not match. Try again.";
758
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
759
                                                            allParams => $allParams,
760
                                                            errorMessage => $errorMessage });
761
            exit();
762
        }
763

    
764
        my $o = $query->param('o');
765

    
766
        my $ldapUsername = $ldapConfig->{$o}{'user'};
767
        my $ldapPassword = $ldapConfig->{$o}{'password'};
768
        my $searchBase = $ldapConfig->{$o}{'base'};
769
        my $dnBase = $ldapConfig->{$o}{'dn'};
770
        
771
        #if main ldap server is down, a html file containing warning message will be returned
772
        my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure("The ldap server " . $ldapurl . " is down!");
773
        
774
        
775
        $ldap->start_tls( verify => 'none');
776
        $ldap->bind( version => 3, dn => $ldapUsername, password => $ldapPassword );
777
        #print "Inserting new entry...\n";
778
        my $dn = 'uid=' . $query->param('uid') . ',' . $dnBase;
779

    
780
        # Create a hashed version of the password
781
        my $shapass = createSeededPassHash($query->param('userPassword'));
782

    
783
        # Do the insertion
784
        my $additions = [ 
785
                'uid'   => $query->param('uid'),
786
                'o'   => $query->param('o'),
787
                'cn'   => join(" ", $query->param('givenName'), 
788
                                    $query->param('sn')),
789
                'sn'   => $query->param('sn'),
790
                'givenName'   => $query->param('givenName'),
791
                'mail' => $query->param('mail'),
792
                'userPassword' => $shapass,
793
                'objectclass' => ['top', 'person', 'organizationalPerson', 
794
                                'inetOrgPerson', 'uidObject' ]
795
            ];
796
        if (defined($query->param('telephoneNumber')) && 
797
            $query->param('telephoneNumber') &&
798
            ! $query->param('telephoneNumber') =~ /^\s+$/) {
799
            $$additions[$#$additions + 1] = 'telephoneNumber';
800
            $$additions[$#$additions + 1] = $query->param('telephoneNumber');
801
        }
802
        if (defined($query->param('title')) && 
803
            $query->param('title') &&
804
            ! $query->param('title') =~ /^\s+$/) {
805
            $$additions[$#$additions + 1] = 'title';
806
            $$additions[$#$additions + 1] = $query->param('title');
807
        }
808
        my $result = $ldap->add ( 'dn' => $dn, 'attr' => [ @$additions ]);
809
    
810
        if ($result->code()) {
811
            fullTemplate( ['registerFailed', 'register'], { stage => "register",
812
                                                            allParams => $allParams,
813
                                                            errorMessage => $result->error });
814
            # TODO SCW was included as separate errors, test this
815
            #$templateVars    = setVars({ stage => "register",
816
            #                     allParams => $allParams });
817
            #$template->process( $templates->{'register'}, $templateVars);
818
        } else {
819
            fullTemplate( ['success'] );
820
        }
821

    
822
        $ldap->unbind;   # take down session
823
    }
824
}
825

    
826
sub handleResponseMessage {
827

    
828
  print "Content-type: text/html\n\n";
829
  my $errorMessage = "You provided invalid input to the script. " .
830
                     "Try again please.";
831
  fullTemplate( [], { stage => $templates->{'stage'},
832
                      errorMessage => $errorMessage });
833
  exit();
834
}
835

    
836
#
837
# perform a simple search against the LDAP database using 
838
# a small subset of attributes of each dn and return it
839
# as a table to the calling browser.
840
#
841
sub handleSimpleSearch {
842

    
843
    my $o = $query->param('o');
844

    
845
    my $ldapurl = $ldapConfig->{$o}{'url'};
846
    my $searchBase = $ldapConfig->{$o}{'base'};
847

    
848
    print "Content-type: text/html\n\n";
849

    
850
    my $allParams = { 
851
                      'cn' => $query->param('cn'),
852
                      'sn' => $query->param('sn'),
853
                      'gn' => $query->param('gn'),
854
                      'o'  => $query->param('o'),
855
                      'facsimiletelephonenumber' 
856
                      => $query->param('facsimiletelephonenumber'),
857
                      'mail' => $query->param('cmail'),
858
                      'telephonenumber' => $query->param('telephonenumber'),
859
                      'title' => $query->param('title'),
860
                      'uid' => $query->param('uid'),
861
                      'ou' => $query->param('ou'),
862
                    };
863

    
864
    # Search LDAP for matching entries that already exist
865
    my $filter = "(" . 
866
                 $query->param('searchField') . "=" .
867
                 "*" .
868
                 $query->param('searchValue') .
869
                 "*" .
870
                 ")";
871

    
872
    my @attrs = [ 'sn', 
873
                  'gn', 
874
                  'cn', 
875
                  'o', 
876
                  'facsimiletelephonenumber', 
877
                  'mail', 
878
                  'telephoneNumber', 
879
                  'title', 
880
                  'uid', 
881
                  'labeledURI', 
882
                  'ou' ];
883

    
884
    my $found = searchDirectory($ldapurl, $searchBase, $filter, \@attrs);
885

    
886
    # Send back the search results
887
    if ($found) {
888
      fullTemplate( ('searchResults'), { stage => "searchresults",
889
                                         allParams => $allParams,
890
                                         foundAccounts => $found });
891
    } else {
892
      $found = "No entries matched your criteria.  Please try again\n";
893

    
894
      fullTemplate( ('searchResults'), { stage => "searchresults",
895
                                         allParams => $allParams,
896
                                         foundAccounts => $found });
897
    }
898

    
899
    exit();
900
}
901

    
902
#
903
# search the LDAP directory to see if a similar account already exists
904
#
905
sub searchDirectory {
906
    my $ldapurl = shift;
907
    my $base = shift;
908
    my $filter = shift;
909
    my $attref = shift;
910

    
911
    my $foundAccounts = 0;
912
    
913
    #if ldap server is down, a html file containing warning message will be returned
914
    my $ldap = Net::LDAP->new($ldapurl, timeout => $timeout) or handleGeneralServerFailure("The ldap server " . $ldapurl . " is down!");
915
    
916
    $ldap->start_tls( verify => 'none');
917
    $ldap->bind( version => 3, anonymous => 1);
918
    my $mesg = $ldap->search (
919
        base   => $base,
920
        filter => $filter,
921
        attrs => @$attref,
922
    );
923

    
924
    if ($mesg->count() > 0) {
925
        $foundAccounts = "";
926
        my $entry;
927
        foreach $entry ($mesg->sorted(['sn'])) {
928
          $foundAccounts .= "<tr>\n<td class=\"main\">\n";
929
          $foundAccounts .= "<a href=\"" unless 
930
                    (!$entry->get_value('labeledURI'));
931
          $foundAccounts .= $entry->get_value('labeledURI') unless
932
                    (!$entry->get_value('labeledURI'));
933
          $foundAccounts .= "\">\n" unless 
934
                    (!$entry->get_value('labeledURI'));
935
          $foundAccounts .= $entry->get_value('givenName');
936
          $foundAccounts .= "</a>\n" unless 
937
                    (!$entry->get_value('labeledURI'));
938
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
939
          $foundAccounts .= "<a href=\"" unless 
940
                    (!$entry->get_value('labeledURI'));
941
          $foundAccounts .= $entry->get_value('labeledURI') unless
942
                    (!$entry->get_value('labeledURI'));
943
          $foundAccounts .= "\">\n" unless 
944
                    (!$entry->get_value('labeledURI'));
945
          $foundAccounts .= $entry->get_value('sn');
946
          $foundAccounts .= "</a>\n";
947
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
948
          $foundAccounts .= $entry->get_value('mail');
949
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
950
          $foundAccounts .= $entry->get_value('telephonenumber');
951
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
952
          $foundAccounts .= $entry->get_value('title');
953
          $foundAccounts .= "\n</td>\n<td class=\"main\">\n";
954
          $foundAccounts .= $entry->get_value('ou');
955
          $foundAccounts .= "\n</td>\n";
956
          $foundAccounts .= "</tr>\n";
957
        }
958
    }
959
    $ldap->unbind;   # take down session
960
    return $foundAccounts;
961
}
962

    
963
sub debug {
964
    my $msg = shift;
965
    
966
    if ($debug) {
967
        print STDERR "$msg\n";
968
    }
969
}
970

    
971
sub handleGeneralServerFailure {
972
    my $errorMessage = shift;
973
    fullTemplate( ['mainServerFailure'], { errorMessage => $errorMessage });
974
    exit(0);   
975
   }
976
    
977
sub setVars {
978
    my $paramVars = shift;
979
    # initialize default parameters 
980
    my $templateVars = { cfg => $cfg,
981
                         styleSkinsPath => $contextUrl . "/style/skins",
982
                         styleCommonPath => $contextUrl . "/style/common",
983
                         contextUrl => $contextUrl,
984
                         orgList => \@orgList,
985
                         config  => $config,
986
    };
987
    
988
    # append customized params
989
    while (my ($k, $v) = each (%$paramVars)) {
990
        $templateVars->{$k} = $v;
991
    }
992
    
993
    return $templateVars;
994
} 
(10-10/14)