ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WEBTOOLS/SecurityModule/SecurityModule.pm
Revision: 1.4
Committed: Mon Feb 26 15:23:35 2007 UTC (18 years, 2 months ago) by dfeichti
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +1 -1 lines
State: FILE REMOVED
Log Message:
- missed migrating these two files

File Contents

# User Rev Content
1 dfeichti 1.1 ################################################################
2     # SecurityModule
3     #
4 dfeichti 1.4 # Version info: $Id: SecurityModule.pm,v 1.3 2007/02/22 12:49:06 dfeichti Exp $
5 dfeichti 1.1 ################################################################
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 dfeichti 1.2 $self->{LOGFILE} = undef; # defaults to stderr (apache err log)
41 dfeichti 1.1 $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 dfeichti 1.2 #TODO test cookie creation time in addition to key validity time?
320 dfeichti 1.1 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 dfeichti 1.2 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
346     $year+=1900;
347     $mon++;
348     my $date = sprintf("$year-%02d-%02d %02d:%02d:%02d",
349     $mon,$mday,$hour,$min,$sec);
350    
351     if (! $self->{LOGFILE}) {
352     print STDERR "$self->{REMOTE_ADDR} $date SecurityModule: $msg\n";
353     return 0;
354     }
355    
356 dfeichti 1.1 if( !$self->{LOGH} ) {
357     if(! ($self->{LOGH} = IO::File->new(">>$self->{LOGFILE}")) ) {
358     $self->{ERRMSG} = "Failed to open logfile " . $self->{LOGFILE};
359 dfeichti 1.2 print STDERR "$date SecurityModule: Failed to open logfile " . $self->{LOGFILE}
360 dfeichti 1.1 . ": $msg\n";
361     return 1;
362     }
363     chmod 0600,$self->{LOGFILE};
364     }
365    
366 dfeichti 1.2
367 dfeichti 1.1 #TODO: better log date formatting
368 dfeichti 1.2 print {$self->{LOGH}} "$self->{REMOTE_ADDR} $date (L$level)" . $msg . "\n";
369 dfeichti 1.1
370     return 0;
371     }
372    
373    
374     sub _createCryptKey {
375     return Crypt::CBC->random_bytes(56);
376     }
377    
378     # if $keyid is given the CIPHER will be initialised with
379     # the key matching this id (if available and still valid)
380     # @return: 0 if ok, 1 for invalid key, 2 for key too old,
381     # 3 for failure to create cipher object
382     sub _initCipher {
383     my $self = shift;
384     my $keyid = shift;
385    
386     my $rv;
387     if ($keyid) {
388     if ($rv = $self->_getCipherKey($keyid)) {
389     if($rv == 1) {
390     $self->{ERRMSG}="invalid cipher key ID $keyid";
391     $self->_log(1,"WARNING: Failed to get cypher keys for ID $keyid");
392     } elsif ($rv == 2) {
393     $self->{ERRMSG}="old cipher key ID $keyid";
394     $self->_log(3,"Refusing old cypher key (ID $keyid)");
395     }
396     return $rv;
397     }
398     } else {
399     $self->_getCipherKey();
400     }
401    
402     $self->{CIPHER} = Crypt::CBC->new( -cipher => 'Blowfish',
403     -literal_key => 1,
404     -key => $self->{CIPHERKEY},
405     -iv => "dummyabc",
406     -header => 'none',
407     -blocksize => 8,
408     -keysize => 56
409     -padding => 'standard'
410     );
411    
412     return 0 if $self->{CIPHER};
413    
414     $self->_log(1,"Error: Failed to create cipher object: keyID $keyid, cipherkey:" .
415     Dumper($self->{CIPHERKEY}));
416     $self->{ERRMSG} = "failed to create cipher object";
417     return 3;
418     }
419    
420     # The random generated initialization vector is prepended to the
421     # encrypted text
422     sub _encrypt {
423     my $self = shift;
424     my $msg = shift;
425    
426     $self->_log(5,"msg to encrypt is >$msg< CIPHER: $self->{CIPHER}");
427     #return "" if $msg == undef or $msg eq "";
428     my $iv = Crypt::CBC->random_bytes(8);
429     $self->{CIPHER}->set_initialization_vector($iv);
430    
431     my $encr = $iv . $self->{CIPHER}->encrypt($msg);
432     my $encr64 = encode_base64($encr);
433    
434     return $encr64;
435     }
436    
437     sub _decrypt{
438     my $self = shift;
439     my $encr64 = shift;
440    
441     my $encr = decode_base64($encr64);
442     my $iv = substr($encr,0,8,"");
443     $self->{CIPHER}->set_initialization_vector($iv);
444    
445     my $decr;
446     eval {
447     $decr = $self->{CIPHER}->decrypt($encr);
448     };
449     if($@) {
450     $self->_log(1,"Error: _decrypt: Failed to decrypt: $@");
451     return undef;
452     }
453    
454     return $decr;
455     }
456    
457     #
458     #
459     # @return: (0,...) for ok, 1 for failed decryption or invalid key, 2 for key too old
460     #
461     sub _decryptCookie {
462     my $self = shift;
463     my $encrcookie = shift;
464    
465     my ($keyid,$encr) = $encrcookie =~ m/^([^|]+)\|(.+)$/s;
466     return 1 if ! $keyid;
467    
468     my $rv;
469     return $rv if ($rv = $self->_initCipher($keyid));
470    
471     my $decr = $self->_decrypt($encr);
472     return 1 if ! $decr;
473     my ($user,$time,$orighash) = split(/\|/,$decr);
474    
475     $self->_log(5,"_decryptCookie: $user | $time | $orighash");
476     return (0,$user,$time,$orighash);
477     }
478    
479     # prepares the cookie payload
480     sub _prepareCookie {
481     my $self = shift;
482    
483     my $cleartxt = join("|",($self->{USERDN},time(),$self->_getOriginatorHash()));
484     my $encr = $self->_encrypt($cleartxt);
485     my $cookie = $self->{KEYID} . "|" . $encr;
486    
487     return $cookie;
488     }
489    
490    
491     #
492     # Authenticate a user based on a password entered in the password form
493     #
494     # @return: 1 for authenticated, 0 for not authenticated
495     sub _passwordAuthen {
496     my $self = shift;
497    
498     return 0 if $self->_initCipher();
499    
500     $self->{ERRMSG} = "";
501    
502     my $username = param("SecModLogin");
503     my $pass = param('SecModPwd');
504    
505     my $dbpasswd = $self->_getUserPasswd($username);
506     my $hash = crypt($pass,substr($dbpasswd,0,2));
507     $self->_log(5,"_passwordAuthen: user $username, pass: $hash, dbpass: $dbpasswd");
508     if ($dbpasswd eq $hash) {
509     $self->{AUTHNSTATE} = "passwd";
510     if ($self->{USERDN} = $self->_getDNfromUsername($username)) {
511 dfeichti 1.2 # for now we use no 'expires' value -expires =>'',
512 dfeichti 1.1 $self->{COOKIE} = cookie(-name => "SecMod",
513     -value => $self->_prepareCookie(),
514     -secure => 1
515     );
516 dfeichti 1.2 $self->_log(4,"Successful password authentication by $username for $self->{USERDN}");
517 dfeichti 1.1 return(1);
518     }
519    
520 dfeichti 1.2 $self->_log(3,"No distinguished name known for user $username");
521 dfeichti 1.1 $self->{ERRMSG} = "No distinguished name known for user $username";
522     return(0);
523     }
524    
525     $self->{ERRMSG} = "Password verification failed";
526 dfeichti 1.2 $self->_log(3,"Password verification failed for user $username");
527 dfeichti 1.1 # TODO: pass on amount of failed attempts in hidden field and redirect after certain
528     # number of failures?
529     return(0);
530     }
531    
532     sub _showPasswdForm {
533     my $self = shift;
534     my $msg = shift;
535    
536     # get rid of any unwanted url parameters
537     (my $url = $self->{CALLER_URL}) =~ s/[&?]SecModLogout=1//;
538     $url =~ s/[&?]SecModPwd=1//;
539    
540     if (ref($self->{PWDFORM_HANDLER}) eq "CODE") {
541     &{$self->{PWDFORM_HANDLER}}($url,$msg);
542     #TODO: should pass all params except SecMod* through to the next page
543     exit 1;
544     }
545     print redirect($self->{PWDFORM_HANDLER} ."?caller_url=" .
546     urlencode($url) . "&msg=" . urlencode($msg));
547     exit 1;
548    
549     }
550    
551     # A default password form
552     sub _passwordForm {
553     my $caller_url = shift;
554     my $msg=shift;
555    
556     my $q = new CGI;
557    
558     print $q->header,$q->start_html;
559     print $q->start_form("POST","$caller_url","application/x-www-form-urlencoded");
560     if ($msg) {
561     print $q->h1("Error: " . $msg);
562     } else {
563     print $q->h1("Log in:");
564     }
565     print $q->p,"Login Name: ",$q->textfield('SecModLogin','',12,20);
566     print $q->p,"Password: ",$q->password_field('SecModPwd','',20,30);
567     print $q->p,$q->submit(-name=>'Submit',
568     -value=>'submit');
569     print $q->endform;
570    
571     print $q->end_html;
572     exit(0);
573     }
574    
575     # A default page for failed certificate access
576     sub _reqcertFailHandler {
577     my $authnstate = shift;
578     my $userdn = shift;
579    
580     my $q = new CGI;
581     print $q->header,$q->start_html;
582     print "Access only with a valid certificate";
583     if ($authnstate eq "passwd") {
584     print " (and you are logged in via password)";
585     } else {
586 dfeichti 1.2 print $q->p,"Your Browser presented this certificate: " . $userdn if $userdn;
587 dfeichti 1.1 }
588     print $q->end_html;
589     exit(0);
590    
591     }
592    
593     1;
594    
595    
596     =pod
597    
598     =head1 NAME
599    
600     SecurityModule.pm - Web Security Module
601    
602     =head1 SYNOPSIS
603    
604     Note: This applies specifically to the MySQL implementation. There is also
605     a SecurityModule::SQLite implementation.
606    
607     use SecurityModule::MySQL;
608 dfeichti 1.3 $sec = new SecurityModule::MySQL({CALLER_URL => $myurl,
609     CONFIG => "/etc/sec/SecMod.conf";
610     LOGLEVEL => 5,
611     KEYVALIDTIME => 7200,
612     PWDFORM_HANDLER => \&myPasswordForm
613     });
614 dfeichti 1.1
615     $ret = $sec->init(); # returns 0 in case of failure
616    
617     $errmsg = $sec->getErrMsg(); # returns error message
618    
619 dfeichti 1.2 # if getCookie() returns a defined value, your page needs to make
620     # sure that this cookie will be set using CGI.pm's
621     # header(-cookie => $cookie ) command
622 dfeichti 1.1 if( ($cookie=$sec->getCookie) ) {
623     print header(-cookie => $cookie );
624     } else {
625     print header();
626     }
627    
628    
629     # Access to authentication / authorization information
630     $state = $sec->getAuthnState(); # returns (failed | cert | passwd)
631     $user_dn = $sec->getDN(); # returns user's distinguished name
632 dfeichti 1.2 $roles = $sec->getRoles(); # returns a hash of roles, each role mapping to a
633 dfeichti 1.1 # list of scopes
634    
635    
636     # Protecting functions: reqAuthnCert() and reqAuthnPasswd()
637     sub my_certificate_protected_function {
638     $sec->reqAuthnCert();
639     ...
640     }
641     sub my_password_protected_function {
642     $sec->reqAuthnPasswd();
643     ...
644     }
645    
646     =head1 DESCRIPTION
647    
648     The SecurityModule handles authentication and authorization to a web site. Users
649 dfeichti 1.2 are identified by a certificate loaded in their browser or by a previously
650 dfeichti 1.1 set cookie that was issued upon a successful password authentication.
651    
652 dfeichti 1.2 Certificate based authentication is the strongest authentication type,
653     so functions protected by the reqAuthnPasswd() method will allow
654     access to certificate authenticated users, but reqAuthnCert() will deny
655     access to password authenticated users.
656    
657 dfeichti 1.1 The SecurityModule was written for a setup where a B<remote Proxy> mediates access
658     to a number of backend servers. The remote proxy handles
659 dfeichti 1.2 the SSL authentication and is required to set the following request headers to
660 dfeichti 1.1 the values of the respective environment variables for this request:
661    
662     B<SSL_CLIENT_VERIFY>,
663     B<SSL_CLIENT_S_DN>,
664     B<HTTPS>.
665    
666     On the backend servers these must be available as environment variables of identical names
667     except for the prefix B<HTTP_>, e.g. B<HTTP_SSL_CLIENT_S_DN>.
668    
669     Since all backend servers are hidden behind the reverse proxy, an authentication
670     cookie is set restrictively to only be sent back to the issueing server. The
671     necessary translation for the proxy is handled transparently by apache's mod_proxy
672     module (needs >= apache-2.2).
673    
674     The SecurityModule can also be run without a reverse proxy in pure SSL mode (by
675     setting the REVPROXY_MODE option to 0), mainly for testing.
676    
677     =head2 Initialization
678    
679     Arguments which can be passed to the constructor:
680    
681     =over 4
682    
683     =item *
684    
685 dfeichti 1.2 B<CALLER_URL>: (Required) URL of the current page that was invoked by
686     the browser, i.e. it must contain the URL which the reverse proxy got
687     before redirecting to the backend server.
688 dfeichti 1.1
689     =item *
690    
691     B<REVPROXY_MODE>: 1 for reverse proxy mode, 0 for basic SSL mode. Default is 1.
692    
693     =item *
694    
695     B<CONFIG>: Filename of a configuration file. The file must contain "option = value"
696     lines as in this example:
697    
698     LOGFILE = /tmp/SecMod.log
699     LOGLEVEL = 5
700     KEYVALIDTIME = 1500
701     # Comments and empty lines are allowed
702     DBHOST = localhost
703     DBPORT = 3306
704     DBNAME = secmod
705     DBUSER = smwriter
706     DBPASS = mypasswd
707     REQCERT_FAIL_HANDLER = https://localhost/bclear/testSecMod/nopermission
708     PWDFORM_HANDLER = https://localhost/bclear/testSecMod/passform
709    
710     B<Note>: The configuration options specified in the constructor will override
711     any options specified in the configuration file.
712    
713     =item *
714    
715     B<KEYVALIDTIME>: Validity time in seconds of generated keys
716    
717     =item *
718    
719     B<PWDFORM_HANDLER>: URL or reference to a function generating a
720     password page. If a function reference is given, two values will be
721     passed into the function: The URL of the present page (so we can get
722     back) and a status message describing why the password form was
723     called. If an URL is given, the two values will be passed using a
724     query string (?caller_url=...&msg=...) in the redirection.
725    
726 dfeichti 1.2 Status messages: password authentication required
727     reauthentication
728     invalid cookie
729 dfeichti 1.1
730     =item *
731    
732     B<REQCERT_FAIL_HANDLER>: URL or reference to a function to call when a page secured by
733     reqAuthnCert() is encountered and the client is not certificate authenticated.
734     Typically, to display some diagnostic message.
735    
736     =item *
737    
738     B<LOGFILE>: Filename of the logfile
739    
740     =item *
741    
742 dfeichti 1.2 B<LOGLEVEL>: Integer value from 0-5
743    
744     0: no log messages at all
745     1: error and security relevant messages only
746     3: Logs password authentications (standard log level)
747     5: debugging messages
748 dfeichti 1.1
749     =item *
750    
751     For the MySQL implementation you can also supply the DB connection parameters
752     B<DBHOST, DBPORT, DBNAME, DBUSER, DBPASS>
753    
754     =item *
755    
756     For the SQLite implementation you only need to supply a B<DBFILE> parameter
757     with the location of the data base file.
758    
759     =back
760    
761 dfeichti 1.3 =head2 Calling the password form via a web page 'Login' link:
762 dfeichti 1.1
763     You can pass B<SecModPwd=1> as a GET variable to any page using the
764     SecurityModule. This will call the handler for / redirect to the password form
765     and insure that the user can return to the same page.
766    
767    
768     =head1 AUTHOR
769    
770     Derek Feichtinger <derek.feichtinger@psi.ch>
771    
772     CMS web interfaces group <hn-cms-webInterfaces@cern.ch>
773    
774    
775     =head1 ISSUES / TODO
776    
777     List of issues to resolve:
778    
779     =over 4
780    
781     =item *
782    
783     POST arguments are not carried across the password form
784    
785     =item *
786    
787 dfeichti 1.2 The mapping from username to certificate distinguished name needs to be established
788     separately.
789 dfeichti 1.1
790     =item *
791    
792     Look at "TODO" comments in the code.
793    
794     =back