ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WEBTOOLS/SecurityModule/SecurityModule.pm
Revision: 1.1
Committed: Wed Feb 7 11:46:10 2007 UTC (18 years, 2 months ago) by dfeichti
Content type: text/plain
Branch: MAIN
Log Message:
- first version of the web security module

File Contents

# Content
1 ################################################################
2 # SecurityModule
3 #
4 # Version info: $Id: SecurityModule.pm,v 1.20 2007/02/07 11:20:09 dfeich Exp $
5 ################################################################
6 package SecurityModule;
7
8 use IO::File;
9 use Crypt::CBC;
10 use MIME::Base64;
11 use Digest::MD5 qw(md5_base64);
12 use Data::Dumper;
13 use CGI qw(cookie param url_param redirect);
14 #use Carp;
15 use strict;
16
17 sub new {
18 my $class = shift;
19 my $options = shift;
20
21 my @settable = qw(CALLER_URL CONFIG LOGFILE LOGLEVEL PWDFORM_HANDLER KEYVALIDTIME
22 REQCERT_FAIL_HANDLER REVPROXY_MODE);
23
24 my $self = {};
25 $self->{AUTHNSTATE} = "failed";
26 $self->{USERDN} = undef;
27 $self->{ROLES} = {};
28
29 $self->{REVPROXY_MODE} = 1; # reverse proxy mode
30
31 $self->{CIPHER} = undef; # cipher object
32 $self->{CIPHERKEY} = undef; # currently active key
33 $self->{KEYID} = undef; # id of current key
34 $self->{KEYVALIDTIME} = 3600; # for how long keys remain valid (in s)
35 $self->{COOKIE} = undef; # holds a cookie to be set
36
37 $self->{CALLER_URL}=""; # true URL (including any proxy transform.)
38
39 $self->{ERRMSG} = "";
40 $self->{LOGFILE} = "/tmp/SecMod.log"; #maybe default to apache log
41 $self->{LOGH} = undef; # Log filehandle
42 $self->{LOGLEVEL} = 1;
43
44 $self->{CONFIG} = undef; # configuration file
45
46 # handler for password page
47 # $self->{PWDFORM_HANDLER} = sub { die "Not implemented"; };
48 $self->{PWDFORM_HANDLER} = \&SecurityModule::_passwordForm;
49
50 # handler for reqAuthnCert failures
51 $self->{REQCERT_FAIL_HANDLER} = \&SecurityModule::_reqcertFailHandler;
52
53
54 bless ($self, $class);
55
56 # treat logfile options seperately first
57 if(exists $options->{LOGFILE}) {
58 $self->{LOGFILE} = $options->{LOGFILE};
59 }
60 if(exists $options->{LOGLEVEL}) {
61 $self->{LOGLEVEL} = $options->{LOGLEVEL};
62 }
63
64 # read the config file and let command line given options override
65 # the config file supplied ones
66 if(exists $options->{CONFIG}) {
67 $self->{CONFIG} = $options->{CONFIG};
68 my $confopts = $self->_readConfigFile();
69 while( my ($opt,$val) = each %$confopts) {
70 $options->{$opt} = $val if ! exists $options->{$opt};
71 }
72 }
73
74 while( my ($opt,$val) = each %$options) {
75 next if ! grep (/$opt/,@settable);
76 $self->{$opt} = $val;
77 delete $options->{$opt};
78 }
79
80 # depending whether we are in reverse proxy mode, choose the
81 # appropriate environment variables for the SSL authentication results
82 if ($self->{REVPROXY_MODE}) {
83 $self->{HTTPS} = $ENV{HTTP_HTTPS};
84 $self->{SSL_CLIENT_VERIFY} = $ENV{HTTP_SSL_CLIENT_VERIFY};
85 $self->{SSL_CLIENT_S_DN} = $ENV{HTTP_SSL_CLIENT_S_DN};
86 $self->{REMOTE_ADDR} = $ENV{HTTP_X_FORWARDED_FOR};
87 } else {
88 $self->{HTTPS} = $ENV{HTTPS};
89 $self->{SSL_CLIENT_VERIFY} = $ENV{SSL_CLIENT_VERIFY};
90 $self->{SSL_CLIENT_S_DN} = $ENV{SSL_CLIENT_S_DN};
91 $self->{REMOTE_ADDR} = $ENV{REMOTE_ADDR};
92 }
93
94 return $self;
95 }
96
97 sub init {
98 my $self = shift;
99
100 return 1 if $self->{HTTPS} ne "on";
101
102 if ($self->{SSL_CLIENT_VERIFY} eq "SUCCESS") {
103 $self->{AUTHNSTATE} = "cert";
104 $self->{USERDN} = $self->{SSL_CLIENT_S_DN};
105 }
106
107 # return if this is the password form
108 return 1 if $self->{CALLER_URL} =~ /^$self->{PWDFORM_HANDLER}.*/;
109
110 my $show_pwdform = 0;
111 # Cookies can override a SSL/cert authentication
112 if((my $cookie = cookie(-name => "SecMod"))) {
113 $self->_log(5,"Found cookie: $cookie");
114 # do cookie authentication
115 $show_pwdform =1 if ! $self->_cookieAuthen($cookie);
116 } elsif (url_param("SecModLogout")){
117 # if there is no cookie, but we still have a Logout directive in the URL
118 # clean it and redirect to self
119 (my $url = $self->{CALLER_URL}) =~ s/[?&]SecModLogout=1//;
120 print redirect($url);
121 }
122
123 # do password authentication of a submitted password form
124 $show_pwdform = $self->_passwordAuthen() ? 0 : 1 if(param("SecModLogin"));
125
126 # don't show form if we are already passwd authenticated
127 $show_pwdform = 1 if (param("SecModPwd") && $self->{AUTHNSTATE} ne "passwd");
128
129 # display the password form
130 if($show_pwdform) {
131 $self->_showPasswdForm($self->{ERRMSG});
132 }
133
134 $self->{ROLES}=$self->_getRolesFromDN($self->{USERDN}) if $self->{AUTHNSTATE} ne "failed";
135
136 return 1;
137 }
138
139 sub reqAuthnPasswd {
140 my $self = shift;
141
142 if ($ENV{HTTP_HTTPS} ne "on") {
143 (my $red = $self->{CALLER_URL}) =~ s!^http:!^https:!;
144 print redirect($red);
145 }
146
147 return if $self->{AUTHNSTATE} eq "cert" || $self->{AUTHNSTATE} eq "passwd";
148 $self->_showPasswdForm("password authentication required");
149 exit(0);
150 }
151
152 sub reqAuthnCert {
153 my $self = shift;
154
155 if ($ENV{HTTP_HTTPS} ne "on") {
156 (my $red = $self->{CALLER_URL}) =~ s!^http:!^https:!;
157 print redirect($red);
158 }
159
160 return if $self->{AUTHNSTATE} eq "cert";
161
162 if (ref($self->{REQCERT_FAIL_HANDLER}) eq "CODE") {
163 &{$self->{REQCERT_FAIL_HANDLER}}($self->{AUTHNSTATE},$self->{USERDN});
164 exit 1;
165 }
166 print redirect($self->{REQCERT_FAIL_HANDLER} ."?caller_url=" .
167 urlencode($self->{CALLER_URL}));
168 exit 1;
169 }
170
171 sub getAuthnState {
172 my $self = shift;
173
174 return $self->{AUTHNSTATE};
175 }
176
177 sub getCookie {
178 my $self = shift;
179 return $self->{COOKIE};
180 }
181
182 sub getDN {
183 my $self = shift;
184 return $self->{USERDN};
185 }
186
187 sub getRoles {
188 my $self = shift;
189 return $self->{ROLES};
190 }
191
192
193 sub getErrMsg {
194 my $self = shift;
195 return $self->{ERRMSG};
196 }
197
198 sub setKeyValidTime {
199 my $self = shift;
200 $self->{KEYVALIDTIME} = shift;
201 }
202
203 sub setLogFile {
204 my $self = shift;
205 $self->{LOGFILE} = shift;
206 }
207
208 sub setLogLevel {
209 my $self = shift;
210 $self->{LOGLEVEL} = shift;
211 }
212
213 sub setPwdHandler {
214 my $self = shift;
215 $self->{PWDFORM_HANDLER} = shift;
216 }
217
218
219 sub DESTROY {
220
221 }
222
223 # CLASS METHODS
224
225 sub urlencode {
226 (my $str = shift) =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
227 return $str;
228 }
229
230 sub urldecode {
231 (my $str = shift) =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
232 return $str;
233 }
234
235
236 ############################################
237 # VIRTUAL METHODS TO BE IMPLEMENTED BY A
238 # SUBCLASS
239
240 # If argument keyid is given, fetches the appropriate key. If
241 # keyid is omitted, returns the a valid key from the DB (if no
242 # valid keys are found, a new one will be generated).
243 # @param: keyid ID of key to be retrieved
244 # @return: 0 if ok, 1 if no such key in DB, 2 if key is too old
245 sub _getCipherKey {
246 die "virtual function _getCipherKey not implemented";
247 }
248
249 # @param: user's DN
250 # @return: hash mapping roles to array of scopes
251 sub _getRolesFromDN {
252 die "virtual function _getRolesFromDN not implemented";
253 }
254
255 # @param: short user name as used in passwd authentication
256 # @return: user's DN
257 sub _getDNfromUsername {
258 die "virtual function _getDNfromUsername not implemented";
259 }
260
261 # @param: short user name as used in passwd authentication
262 # @return: password hash
263 sub _getUserPasswd {
264 die "virtual function _getUserPasswd not implemented";
265 }
266
267 ############################################
268 # PRIVATE METHODS
269
270
271 sub _getOriginatorHash {
272 my $self = shift;
273 $self->_log(5,"_getOriginatorHash: REMOTE_ADDR=".$self->{REMOTE_ADDR});
274 return md5_base64($self->{REMOTE_ADDR},
275 $ENV{HTTP_USER_AGENT});
276 }
277
278 # very primitive configuration file reader
279 sub _readConfigFile {
280 my $self = shift;
281
282 my $conf={};
283
284 open(CONF,"<$self->{CONFIG}") or die "Failed to open config file " . $self->{CONFIG};
285
286 while(my $line = <CONF>) {
287 next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
288
289 if( my ($opt,$val) = $line =~ m/^\s*([A-Z_]+)\s*=\s*([^\s]+)\s*$/) {
290 $conf->{$opt} = $val;
291 }
292 }
293 close CONF;
294 $self->_log(5,"Config File: " . Dumper($conf));
295 return $conf;
296 }
297
298 # Does authentication of a presented cookie. Also implements a
299 # logout feature by setting an obsolete cookie
300 #
301 # @return 1 for a correct cookie, 0 if an invalid cookie was found
302 # also returns 1 if a "Logout" directive was found
303 sub _cookieAuthen {
304 my $self = shift;
305 my $cookie = shift;
306
307 # A 'logout' is performed if a parameter SecModLogout is found
308 # basically resulting in destroying the cookie
309 if(param("SecModLogout")) {
310 $self->{COOKIE} = cookie(-name => "SecMod",
311 -value =>"",
312 -expires => "-1h",
313 -secure => 1
314 );
315 return(1);
316 }
317
318 my ($state,$user,$time,$orighash) = $self->_decryptCookie($cookie);
319 #TODO test cookie time in addition to key validity time?
320 if($state==0 && $orighash eq $self->_getOriginatorHash()) {
321 $self->{AUTHNSTATE} = "passwd";
322 $self->{USERDN} = $user;
323 return(1);
324 }
325 $self->_log(5,"_cookieAuthen: Failed: orighash=" . $self->_getOriginatorHash());
326
327 if ($state == 2) {
328 $self->{ERRMSG}="reauthentication";
329 return(0);
330 }
331
332 $self->{ERRMSG}="invalid cookie";
333 $self->_log(1,"INVALID COOKIE from .$self->{REMOTE_ADDR}: $cookie");
334 return(0)
335 }
336
337 # @return: 0 if all went correct
338 sub _log {
339 my $self = shift;
340 my $level = shift;
341 my $msg = shift;
342
343 return if($level>$self->{LOGLEVEL});
344
345 if( !$self->{LOGH} ) {
346 if(! ($self->{LOGH} = IO::File->new(">>$self->{LOGFILE}")) ) {
347 $self->{ERRMSG} = "Failed to open logfile " . $self->{LOGFILE};
348 print STDERR "Failed to open logfile " . $self->{LOGFILE}
349 . ": $msg\n";
350 return 1;
351 }
352 chmod 0600,$self->{LOGFILE};
353 }
354
355 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
356 $year+=1900;
357 $mon++;
358 #TODO: better log date formatting
359 print {$self->{LOGH}} "$year-$mon-$mday $hour:$min:$sec (L$level)" . $msg . "\n";
360
361 return 0;
362 }
363
364
365 sub _createCryptKey {
366 return Crypt::CBC->random_bytes(56);
367 }
368
369 # if $keyid is given the CIPHER will be initialised with
370 # the key matching this id (if available and still valid)
371 # @return: 0 if ok, 1 for invalid key, 2 for key too old,
372 # 3 for failure to create cipher object
373 sub _initCipher {
374 my $self = shift;
375 my $keyid = shift;
376
377 my $rv;
378 if ($keyid) {
379 if ($rv = $self->_getCipherKey($keyid)) {
380 if($rv == 1) {
381 $self->{ERRMSG}="invalid cipher key ID $keyid";
382 $self->_log(1,"WARNING: Failed to get cypher keys for ID $keyid");
383 } elsif ($rv == 2) {
384 $self->{ERRMSG}="old cipher key ID $keyid";
385 $self->_log(3,"Refusing old cypher key (ID $keyid)");
386 }
387 return $rv;
388 }
389 } else {
390 $self->_getCipherKey();
391 }
392
393 $self->{CIPHER} = Crypt::CBC->new( -cipher => 'Blowfish',
394 -literal_key => 1,
395 -key => $self->{CIPHERKEY},
396 -iv => "dummyabc",
397 -header => 'none',
398 -blocksize => 8,
399 -keysize => 56
400 -padding => 'standard'
401 );
402
403 return 0 if $self->{CIPHER};
404
405 $self->_log(1,"Error: Failed to create cipher object: keyID $keyid, cipherkey:" .
406 Dumper($self->{CIPHERKEY}));
407 $self->{ERRMSG} = "failed to create cipher object";
408 return 3;
409 }
410
411 # The random generated initialization vector is prepended to the
412 # encrypted text
413 sub _encrypt {
414 my $self = shift;
415 my $msg = shift;
416
417 $self->_log(5,"msg to encrypt is >$msg< CIPHER: $self->{CIPHER}");
418 #return "" if $msg == undef or $msg eq "";
419 my $iv = Crypt::CBC->random_bytes(8);
420 $self->{CIPHER}->set_initialization_vector($iv);
421
422 my $encr = $iv . $self->{CIPHER}->encrypt($msg);
423 my $encr64 = encode_base64($encr);
424
425 return $encr64;
426 }
427
428 sub _decrypt{
429 my $self = shift;
430 my $encr64 = shift;
431
432 my $encr = decode_base64($encr64);
433 my $iv = substr($encr,0,8,"");
434 $self->{CIPHER}->set_initialization_vector($iv);
435
436 my $decr;
437 eval {
438 $decr = $self->{CIPHER}->decrypt($encr);
439 };
440 if($@) {
441 $self->_log(1,"Error: _decrypt: Failed to decrypt: $@");
442 return undef;
443 }
444
445 return $decr;
446 }
447
448 #
449 #
450 # @return: (0,...) for ok, 1 for failed decryption or invalid key, 2 for key too old
451 #
452 sub _decryptCookie {
453 my $self = shift;
454 my $encrcookie = shift;
455
456 my ($keyid,$encr) = $encrcookie =~ m/^([^|]+)\|(.+)$/s;
457 return 1 if ! $keyid;
458
459 my $rv;
460 return $rv if ($rv = $self->_initCipher($keyid));
461
462 my $decr = $self->_decrypt($encr);
463 return 1 if ! $decr;
464 my ($user,$time,$orighash) = split(/\|/,$decr);
465
466 $self->_log(5,"_decryptCookie: $user | $time | $orighash");
467 return (0,$user,$time,$orighash);
468 }
469
470 # prepares the cookie payload
471 sub _prepareCookie {
472 my $self = shift;
473
474 my $cleartxt = join("|",($self->{USERDN},time(),$self->_getOriginatorHash()));
475 my $encr = $self->_encrypt($cleartxt);
476 my $cookie = $self->{KEYID} . "|" . $encr;
477
478 return $cookie;
479 }
480
481
482 #
483 # Authenticate a user based on a password entered in the password form
484 #
485 # @return: 1 for authenticated, 0 for not authenticated
486 sub _passwordAuthen {
487 my $self = shift;
488
489 return 0 if $self->_initCipher();
490
491 $self->{ERRMSG} = "";
492
493 my $username = param("SecModLogin");
494 my $pass = param('SecModPwd');
495
496 my $dbpasswd = $self->_getUserPasswd($username);
497 my $hash = crypt($pass,substr($dbpasswd,0,2));
498 $self->_log(5,"_passwordAuthen: user $username, pass: $hash, dbpass: $dbpasswd");
499 if ($dbpasswd eq $hash) {
500 $self->{AUTHNSTATE} = "passwd";
501 if ($self->{USERDN} = $self->_getDNfromUsername($username)) {
502 #TODO: set correct expires value
503 $self->{COOKIE} = cookie(-name => "SecMod",
504 -value => $self->_prepareCookie(),
505 -expires =>'+5h',
506 -secure => 1
507 );
508 return(1);
509 }
510
511
512 #TODO: if the entry is not present we should probably set it based
513 # on the current USERDN from the SSL authentication
514 $self->{ERRMSG} = "No distinguished name known for user $username";
515 return(0);
516 }
517
518 $self->{ERRMSG} = "Password verification failed";
519 # TODO: pass on amount of failed attempts in hidden field and redirect after certain
520 # number of failures?
521 return(0);
522 }
523
524 sub _showPasswdForm {
525 my $self = shift;
526 my $msg = shift;
527
528 # get rid of any unwanted url parameters
529 (my $url = $self->{CALLER_URL}) =~ s/[&?]SecModLogout=1//;
530 $url =~ s/[&?]SecModPwd=1//;
531
532 if (ref($self->{PWDFORM_HANDLER}) eq "CODE") {
533 &{$self->{PWDFORM_HANDLER}}($url,$msg);
534 #TODO: should pass all params except SecMod* through to the next page
535 exit 1;
536 }
537 print redirect($self->{PWDFORM_HANDLER} ."?caller_url=" .
538 urlencode($url) . "&msg=" . urlencode($msg));
539 exit 1;
540
541 }
542
543 # A default password form
544 sub _passwordForm {
545 my $caller_url = shift;
546 my $msg=shift;
547
548 my $q = new CGI;
549
550 print $q->header,$q->start_html;
551 print $q->start_form("POST","$caller_url","application/x-www-form-urlencoded");
552 if ($msg) {
553 print $q->h1("Error: " . $msg);
554 } else {
555 print $q->h1("Log in:");
556 }
557 print $q->p,"Login Name: ",$q->textfield('SecModLogin','',12,20);
558 print $q->p,"Password: ",$q->password_field('SecModPwd','',20,30);
559 print $q->p,$q->submit(-name=>'Submit',
560 -value=>'submit');
561 print $q->endform;
562
563 print $q->end_html;
564 exit(0);
565 }
566
567 # A default page for failed certificate access
568 sub _reqcertFailHandler {
569 my $authnstate = shift;
570 my $userdn = shift;
571
572 my $q = new CGI;
573 print $q->header,$q->start_html;
574 print "Access only with a valid certificate";
575 if ($authnstate eq "passwd") {
576 print " (and you are logged in via password)";
577 } else {
578 print $q->p,"Your Browser presented: " . $userdn if $userdn;
579 }
580 print $q->end_html;
581 exit(0);
582
583 }
584
585 1;
586
587
588 =pod
589
590 =head1 NAME
591
592 SecurityModule.pm - Web Security Module
593
594 =head1 SYNOPSIS
595
596 Note: This applies specifically to the MySQL implementation. There is also
597 a SecurityModule::SQLite implementation.
598
599 use SecurityModule::MySQL;
600 $sec = new SecurityModule::MySQL;
601 my $sec = new SecurityModule::MySQL({CALLER_URL => $myurl,
602 CONFIG => "/etc/sec/SecMod.conf";
603 LOGLEVEL => 5,
604 KEYVALIDTIME => 7200,
605 PWDFORM_HANDLER => \&myPasswordForm
606 });
607
608 $ret = $sec->init(); # returns 0 in case of failure
609
610 $errmsg = $sec->getErrMsg(); # returns error message
611
612 # if getCookie() is defined, your page needs to make sure that this
613 # cookie will be set using CGI.pm's header(-cookie => $cookie )
614 if( ($cookie=$sec->getCookie) ) {
615 print header(-cookie => $cookie );
616 } else {
617 print header();
618 }
619
620
621 # Access to authentication / authorization information
622 $state = $sec->getAuthnState(); # returns (failed | cert | passwd)
623 $user_dn = $sec->getDN(); # returns user's distinguished name
624 $roles = $sec->getRoles(); # returns a hash of roles, each role pointing to a
625 # list of scopes
626
627
628 # Protecting functions: reqAuthnCert() and reqAuthnPasswd()
629 sub my_certificate_protected_function {
630 $sec->reqAuthnCert();
631 ...
632 }
633 sub my_password_protected_function {
634 $sec->reqAuthnPasswd();
635 ...
636 }
637
638 =head1 DESCRIPTION
639
640 The SecurityModule handles authentication and authorization to a web site. Users
641 are identified via a certificate loaded in their browser or via a previously
642 set cookie that was issued upon a successful password authentication.
643
644 The SecurityModule was written for a setup where a B<remote Proxy> mediates access
645 to a number of backend servers. The remote proxy handles
646 the SSL authentication and sets the following request headers to
647 the values of the respective environment variables for this request:
648
649 B<SSL_CLIENT_VERIFY>,
650 B<SSL_CLIENT_S_DN>,
651 B<HTTPS>.
652
653 On the backend servers these must be available as environment variables of identical names
654 except for the prefix B<HTTP_>, e.g. B<HTTP_SSL_CLIENT_S_DN>.
655
656 Since all backend servers are hidden behind the reverse proxy, an authentication
657 cookie is set restrictively to only be sent back to the issueing server. The
658 necessary translation for the proxy is handled transparently by apache's mod_proxy
659 module (needs >= apache-2.2).
660
661 The SecurityModule can also be run without a reverse proxy in pure SSL mode (by
662 setting the REVPROXY_MODE option to 0), mainly for testing.
663
664 =head2 Initialization
665
666 Arguments which can be passed to the constructor:
667
668 =over 4
669
670 =item *
671
672 B<CALLER_URL>: URL of the current page that was invoked by the browser, i.e. it
673 must contain the URL which the reverse proxy got before redirecting to the backend
674 server.
675
676 =item *
677
678 B<REVPROXY_MODE>: 1 for reverse proxy mode, 0 for basic SSL mode. Default is 1.
679
680 =item *
681
682 B<CONFIG>: Filename of a configuration file. The file must contain "option = value"
683 lines as in this example:
684
685 LOGFILE = /tmp/SecMod.log
686 LOGLEVEL = 5
687 KEYVALIDTIME = 1500
688 # Comments and empty lines are allowed
689 DBHOST = localhost
690 DBPORT = 3306
691 DBNAME = secmod
692 DBUSER = smwriter
693 DBPASS = mypasswd
694 REQCERT_FAIL_HANDLER = https://localhost/bclear/testSecMod/nopermission
695 PWDFORM_HANDLER = https://localhost/bclear/testSecMod/passform
696
697 B<Note>: The configuration options specified in the constructor will override
698 any options specified in the configuration file.
699
700 =item *
701
702 B<KEYVALIDTIME>: Validity time in seconds of generated keys
703
704 =item *
705
706 B<PWDFORM_HANDLER>: URL or reference to a function generating a
707 password page. If a function reference is given, two values will be
708 passed into the function: The URL of the present page (so we can get
709 back) and a status message describing why the password form was
710 called. If an URL is given, the two values will be passed using a
711 query string (?caller_url=...&msg=...) in the redirection.
712
713 Status messages: password authentication required, reauthentication, invalid cookie
714
715 =item *
716
717 B<REQCERT_FAIL_HANDLER>: URL or reference to a function to call when a page secured by
718 reqAuthnCert() is encountered and the client is not certificate authenticated.
719 Typically, to display some diagnostic message.
720
721 =item *
722
723 B<LOGFILE>: Filename of the logfile
724
725 =item *
726
727 B<LOGLEVEL>: (from 1 to 5)
728
729 =item *
730
731 For the MySQL implementation you can also supply the DB connection parameters
732 B<DBHOST, DBPORT, DBNAME, DBUSER, DBPASS>
733
734 =item *
735
736 For the SQLite implementation you only need to supply a B<DBFILE> parameter
737 with the location of the data base file.
738
739 =back
740
741 =head2 Calling the password form via a page "Login" link:
742
743 You can pass B<SecModPwd=1> as a GET variable to any page using the
744 SecurityModule. This will call the handler for / redirect to the password form
745 and insure that the user can return to the same page.
746
747
748 =head1 AUTHOR
749
750 Derek Feichtinger <derek.feichtinger@psi.ch>
751
752 CMS web interfaces group <hn-cms-webInterfaces@cern.ch>
753
754
755 =head1 ISSUES / TODO
756
757 List of issues to resolve:
758
759 =over 4
760
761 =item *
762
763 POST arguments are not carried across the password form
764
765 =item *
766
767 The mapping from username to certificate distinguished name needs to be established,
768 either by letting a cert authenticated user log in and storing his username, or by
769 providing a special page where the user can password authenticate and establish the
770 mapping.
771
772 =item *
773
774 Look at "TODO" comments in the code.
775
776 =back