login/login pages and cookie/session-based auth
authorIvan Kohler <ivan@freeside.biz>
Fri, 5 Apr 2013 08:03:44 +0000 (01:03 -0700)
committerIvan Kohler <ivan@freeside.biz>
Fri, 5 Apr 2013 08:03:44 +0000 (01:03 -0700)
19 files changed:
FS/FS/Auth/external.pm [new file with mode: 0644]
FS/FS/Auth/internal.pm [new file with mode: 0644]
FS/FS/Auth/legacy.pm [new file with mode: 0644]
FS/FS/AuthCookieHandler.pm [new file with mode: 0644]
FS/FS/CGI.pm
FS/FS/Mason/Request.pm
FS/FS/UID.pm
FS/FS/access_user/internal.pm [deleted file]
FS/FS/access_user/legacy.pm [deleted file]
FS/MANIFEST
Makefile
eg/access_user-external_auth.pm
htetc/freeside-base2.conf
htetc/htpasswd.logout [deleted file]
httemplate/autohandler
httemplate/elements/header.html
httemplate/elements/logout.html [deleted file]
httemplate/loginout/login.html [new file with mode: 0644]
httemplate/loginout/logout.html

diff --git a/FS/FS/Auth/external.pm b/FS/FS/Auth/external.pm
new file mode 100644 (file)
index 0000000..d2bc746
--- /dev/null
@@ -0,0 +1,18 @@
+packages FS::Auth::external;
+#use base qw( FS::Auth );
+
+use strict;
+
+sub autocreate {
+  my $username = shift;
+  my $access_user = new FS::access_user {
+    'username' => $username,
+    #'_password' => #XXX something random so a switch to internal auth doesn't
+                    #let people on?
+  };
+  my $error = $access_user->insert;
+  #die $error if $error;
+}
+
+1;
+
diff --git a/FS/FS/Auth/internal.pm b/FS/FS/Auth/internal.pm
new file mode 100644 (file)
index 0000000..86fddd2
--- /dev/null
@@ -0,0 +1,15 @@
+package FS::Auth::internal;
+#use base qw( FS::Auth );
+
+use strict;
+
+sub authenticate {
+  my( $username, $check_password ) = @_;
+
+
+}
+
+sub change_password {
+}
+
+1;
diff --git a/FS/FS/Auth/legacy.pm b/FS/FS/Auth/legacy.pm
new file mode 100644 (file)
index 0000000..7212202
--- /dev/null
@@ -0,0 +1,25 @@
+package FS::Auth::legacy;
+#use base qw( FS::Auth ); #::internal ?
+
+use strict;
+use Apache::Htpasswd;
+
+#substitute in?  we're trying to make it go away...
+my $htpasswd_file = '/usr/local/etc/freeside/htpasswd';
+
+sub authenticate {
+  my($self, $username, $check_password ) = @_;
+
+  Apache::Htpasswd->new( { passwdFile => $htpasswd_file,
+                           ReadOnly   => 1,
+                         }
+    )->htCheckPassword($username, $check_password);
+}
+
+#don't support this in legacy?  change in both htpasswd and database like 3.x
+# for easier transitioning?  hoping its really only me+employees that have a
+# mismatch in htpasswd vs access_user, so maybe that's not necessary
+#sub change_password {
+#}
+
+1;
diff --git a/FS/FS/AuthCookieHandler.pm b/FS/FS/AuthCookieHandler.pm
new file mode 100644 (file)
index 0000000..a4a3118
--- /dev/null
@@ -0,0 +1,56 @@
+package FS::AuthCookieHandler;
+use base qw( Apache2::AuthCookie );
+
+use strict;
+use Digest::SHA qw( sha1_hex );
+use FS::UID qw( adminsuidsetup );
+
+my $secret = "XXX temporary"; #XXX move to a DB session with random number as key
+
+my $module = 'legacy'; #XXX i am set in a conf somehow?  or a config file
+
+sub authen_cred {
+  my( $self, $r, $username, $password ) = @_;
+
+  if ( _is_valid_user($username, $password) ) {
+      warn "authenticated $username from ". $r->connection->remote_ip. "\n";
+      adminsuidsetup($username);
+      my $session_key =
+        $username . '::' . sha1_hex( $username, $secret );
+      return $session_key;
+  } else {
+      warn "failed authentication $username from ". $r->connection->remote_ip. "\n";
+  }
+
+  return undef; #?
+}
+
+sub _is_valid_user {
+  my( $username, $password ) = @_;
+  my $class = 'FS::Auth::'.$module;
+
+  #earlier?
+  eval "use $class;";
+  die $@ if $@;
+
+  $class->authenticate($username, $password);
+
+}
+
+sub authen_ses_key {
+  my( $self, $r, $session_key ) = @_;
+
+  my ($username, $mac) = split /::/, $session_key;
+
+  if ( sha1_hex( $username, $secret ) eq $mac ) {
+    adminsuidsetup($username);
+    return $username;
+  } else {
+    warn "bad session $session_key from ". $r->connection->remote_ip. "\n";
+  }
+
+  return undef;
+
+}
+
+1;
index 972625f..5ac31db 100644 (file)
@@ -6,7 +6,7 @@ use Exporter;
 use CGI;
 use URI::URL;
 #use CGI::Carp qw(fatalsToBrowser);
-use FS::UID;
+use FS::UID qw( cgi );
 
 @ISA = qw(Exporter);
 @EXPORT_OK = qw( header menubar idiot eidiot popurl rooturl table itable ntable
@@ -232,7 +232,7 @@ sub rooturl {
     $url_string = shift;
   } else {
     # better to start with the client-provided URL
-    my $cgi = &FS::UID::cgi;
+    my $cgi = cgi;
     $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
   }
 
@@ -244,7 +244,7 @@ sub rooturl {
   $url_string =~
     s{
        /
-       (browse|config|docs|edit|graph|misc|search|view|pref|elements|rt|torrus)
+       (browse|config|docs|edit|graph|misc|search|view|loginout|pref|elements|rt|torrus)
        (/process)?
        ([\w\-\.\/]*)
        $
index 36c46dc..1e2555a 100644 (file)
@@ -69,7 +69,7 @@ sub freeside_setup {
       FS::Trace->log('    handling RT REST/NoAuth file');
 
       package HTML::Mason::Commands; #?
-      use FS::UID qw( adminsuidsetup );
+      use FS::UID qw( adminsuidsetup setcgi );
 
       #need to log somebody in for the mail gw
 
@@ -86,14 +86,15 @@ sub freeside_setup {
       package HTML::Mason::Commands;
       use vars qw( $cgi $p $fsurl ); # $lh ); #not using /mt
       use Encode;
-      use FS::UID qw( cgisuidsetup );
+      #use FS::UID qw( cgisuidsetup );
       use FS::CGI qw( popurl rooturl );
 
       if ( $mode eq 'apache' ) {
         $cgi = new CGI;
-        FS::Trace->log('    cgisuidsetup');
-        &cgisuidsetup($cgi);
-        #&cgisuidsetup($r);
+        setcgi($cgi);
+
+        #cgisuidsetup is gone, adminsuidsetup is now done in AuthCookieHandler
+
         $fsurl = rooturl();
         $p = popurl(2);
       } elsif ( $mode eq 'standalone' ) {
@@ -106,19 +107,19 @@ sub freeside_setup {
         die "unknown mode $mode";
       }
 
-    FS::Trace->log('    UTF-8-decoding form data');
-    #
-    foreach my $param ( $cgi->param ) {
-      my @values = $cgi->param($param);
-      next if $cgi->uploadInfo($values[0]);
-      #warn $param;
-      @values = map decode(utf8=>$_), @values;
-      $cgi->param($param, @values);
+      FS::Trace->log('    UTF-8-decoding form data');
+      #
+      foreach my $param ( $cgi->param ) {
+        my @values = $cgi->param($param);
+        next if $cgi->uploadInfo($values[0]);
+        #warn $param;
+        @values = map decode(utf8=>$_), @values;
+        $cgi->param($param, @values);
+      }
+
     }
-    
-  }
 
-  FS::Trace->log('    done');
+    FS::Trace->log('    done');
 
 }
 
index 67bb75f..44d3870 100644 (file)
@@ -6,19 +6,18 @@ use vars qw(
   $secrets $datasrc $db_user $db_pass $schema $dbh $driver_name
   $AutoCommit %callback @callback $callback_hack $use_confcompat
 );
-use subs qw(
-  getsecrets cgisetotaker
-);
+use subs qw( getsecrets );
 use Exporter;
-use Carp qw(carp croak cluck confess);
+use Carp qw( carp croak cluck confess );
 use DBI;
 use IO::File;
 use FS::CurrentUser;
 
 @ISA = qw(Exporter);
-@EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup
-                getotaker dbh datasrc getsecrets driver_name myconnect
-                use_confcompat);
+@EXPORT_OK = qw( checkeuid checkruid cgi setcgi adminsuidsetup forksuidsetup
+                 getotaker dbh datasrc getsecrets driver_name myconnect
+                 use_confcompat
+               );
 
 $DEBUG = 0;
 $me = '[FS::UID]';
@@ -38,13 +37,9 @@ FS::UID - Subroutines for database login and assorted other stuff
 
 =head1 SYNOPSIS
 
-  use FS::UID qw(adminsuidsetup cgisuidsetup dbh datasrc getotaker
-  checkeuid checkruid);
-
-  adminsuidsetup $user;
+  use FS::UID qw(adminsuidsetup dbh datasrc getotaker checkeuid checkruid);
 
-  $cgi = new CGI;
-  $dbh = cgisuidsetup($cgi);
+  $dbh = adminsuidsetup $user;
 
   $dbh = dbh;
 
@@ -194,35 +189,26 @@ sub install_callback {
   &{$callback} if $dbh;
 }
 
-=item cgisuidsetup CGI_object
+=item cgi
 
-Takes a single argument, which is a CGI (see L<CGI>) or Apache (see L<Apache>)
-object (CGI::Base is depriciated).  Runs cgisetotaker and then adminsuidsetup.
+Returns the CGI (see L<CGI>) object.
 
 =cut
 
-sub cgisuidsetup {
-  $cgi=shift;
-  if ( $cgi->isa('CGI::Base') ) {
-    carp "Use of CGI::Base is depriciated";
-  } elsif ( $cgi->isa('Apache') ) {
-
-  } elsif ( ! $cgi->isa('CGI') ) {
-    croak "fatal: unrecognized object $cgi";
-  }
-  cgisetotaker; 
-  adminsuidsetup($user);
+sub cgi {
+  carp "warning: \$FS::UID::cgi is undefined" unless defined($cgi);
+  #carp "warning: \$FS::UID::cgi isa Apache" if $cgi && $cgi->isa('Apache');
+  $cgi;
 }
 
-=item cgi
+=item cgi CGI_OBJECT
 
-Returns the CGI (see L<CGI>) object.
+Sets the CGI (see L<CGI>) object.
 
 =cut
 
-sub cgi {
-  carp "warning: \$FS::UID::cgi isa Apache" if $cgi->isa('Apache');
-  $cgi;
+sub setcgi {
+  $cgi = shift;
 }
 
 =item dbh
@@ -270,29 +256,6 @@ sub getotaker {
   $user;
 }
 
-=item cgisetotaker
-
-Sets and returns the CGI REMOTE_USER.  $cgi should be defined as a CGI.pm
-object (see L<CGI>) or an Apache object (see L<Apache>).  Support for CGI::Base
-and derived classes is depriciated.
-
-=cut
-
-sub cgisetotaker {
-  if ( $cgi && $cgi->isa('CGI::Base') && defined $cgi->var('REMOTE_USER')) {
-    carp "Use of CGI::Base is depriciated";
-    $user = lc ( $cgi->var('REMOTE_USER') );
-  } elsif ( $cgi && $cgi->isa('CGI') && defined $cgi->remote_user ) {
-    $user = lc ( $cgi->remote_user );
-  } elsif ( $cgi && $cgi->isa('Apache') ) {
-    $user = lc ( $cgi->connection->user );
-  } else {
-    die "fatal: Can't get REMOTE_USER! for cgi $cgi - you need to setup ".
-        "Apache user authentication as documented in the installation instructions";
-  }
-  $user;
-}
-
 =item checkeuid
 
 Returns true if effective UID is that of the freeside user.
@@ -390,8 +353,7 @@ Too many package-global variables.
 
 Not OO.
 
-No capabilities yet.  When mod_perl and Authen::DBI are implemented, 
-cgisuidsetup will go away as well.
+No capabilities yet. (What does this mean again?)
 
 Goes through contortions to support non-OO syntax with multiple datasrc's.
 
diff --git a/FS/FS/access_user/internal.pm b/FS/FS/access_user/internal.pm
deleted file mode 100644 (file)
index 94f932d..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package FS::access_user::internal;
-use base qw( FS::access_user );
-
-use strict;
-
-sub authenticate {
-  my( $username, $check_password ) = @_;
-
-
-}
-
-sub change_password {
-}
-
-1;
diff --git a/FS/FS/access_user/legacy.pm b/FS/FS/access_user/legacy.pm
deleted file mode 100644 (file)
index f8dcdc0..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package FS::access_user::legacy;
-use base qw( FS::access_user ); #::internal ?
-
-use strict;
-
-sub authenticate {
-  my( $username, $check_password ) = @_;
-
-
-}
-
-sub change_password {
-}
-
-1;
index 95b11f8..43f36ab 100644 (file)
@@ -27,6 +27,10 @@ bin/freeside-sqlradius-seconds
 bin/freeside-torrus-srvderive
 FS.pm
 FS/AccessRight.pm
+FS/AuthCookieHandler.pm
+FS/Auth/external.pm
+FS/Auth/internal.pm
+FS/Auth/legacy.pm
 FS/CGI.pm
 FS/InitHandler.pm
 FS/ClientAPI.pm
index dd7adb0..5e42531 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -179,7 +179,6 @@ install-docs: docs
        " ${MASON_HANDLER} || true
        mkdir -p ${FREESIDE_EXPORT}/profile
        chown freeside ${FREESIDE_EXPORT}/profile
-       cp htetc/htpasswd.logout ${FREESIDE_CONF}
        [ ! -e ${MASONDATA} ] && mkdir ${MASONDATA} || true
        chown -R freeside ${MASONDATA}
 
index 47fa7d9..bc6e23a 100644 (file)
@@ -1,5 +1,6 @@
 package FS::access_user::external_auth;
-use base qw( FS::access_user );
+use base qw( FS::access_user::external ); #inherit from ::external for
+                                          # autocreation
 
 use strict;
 
index 49b4a24..1bbe90a 100644 (file)
@@ -14,28 +14,48 @@ PerlRequire "%%%MASON_HANDLER%%%"
 #
 AddDefaultCharset UTF-8
 
+PerlModule FS::AuthCookieHandler
+
+#XXX need to also work properly for installs w/o /freeside/ in path
+PerlSetVar FreesideLoginScript /freeside/loginout/login.html
+
+#PerlSetVar FreesideEverSecure 1
+PerlSetVar FreesideHttpOnly 1
+
 <Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
-AuthName Freeside
-AuthType Basic
-AuthUserFile %%%FREESIDE_CONF%%%/htpasswd
-require valid-user
-<Files ~ "(\.cgi|\.html)$">
-SetHandler perl-script
-PerlHandler HTML::Mason
+
+    AuthName Freeside
+    AuthType FS::AuthCookieHandler
+    PerlAuthenHandler FS::AuthCookieHandler->authenticate
+    PerlAuthzHandler  FS::AuthCookieHandler->authorize
+    require valid-user
+
+    <Files ~ "(\.cgi|\.html)$">
+        SetHandler perl-script
+        PerlHandler HTML::Mason
+    </Files>
+
+</Directory>
+
+<Files login>
+    AuthName Freeside
+    AuthType FS::AuthCookieHandler
+    SetHandler perl-script
+    PerlHandler FS::AuthCookieHandler->login
 </Files>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/elements/>
+    <Files "freeside.css">
+        Satisfy any
+    </Files>
 </Directory>
+
 <Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Helpers/>
-SetHandler perl-script
-PerlHandler HTML::Mason
+    SetHandler perl-script
+    PerlHandler HTML::Mason
 </Directory>
 
-<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/loginout>
-AuthName Freeside
-AuthType Basic
-AuthUserFile %%%FREESIDE_CONF%%%/htpasswd.logout
-require valid-user
-<Files ~ "(\.cgi|\.html)$">
-SetHandler default-handler
-</Files>
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/REST/1.0/NoAuth/>
+    Satisfy any
 </Directory>
 
diff --git a/htetc/htpasswd.logout b/htetc/htpasswd.logout
deleted file mode 100644 (file)
index 3523f23..0000000
+++ /dev/null
@@ -1 +0,0 @@
-magic:Jgvaxb502SIqQ
index c326e3e..b5b1071 100644 (file)
@@ -46,5 +46,5 @@ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) {
 
 </%filter>
 <%cleanup>
-   dbh->commit();
+   dbh->commit() if dbh;
 </%cleanup>
index c6ad3c3..4f5015e 100644 (file)
@@ -2,18 +2,18 @@
 
 Example:
 
-  include( '/elements/header.html',
-           {
-             'title'   => 'Title',
-             'menubar' => \@menubar,
-             'etc'     => '', #included in <BODY> tag, for things like onLoad=
-             'head'    => '', #included before closing </HEAD> tag
-             'nobr'    => 0,  #1 for no <BR><BR> after the title
-           }
-         );
-
-  #old-style
-  include( '/elements/header.html', 'Title', $menubar, $etc, $head);
+  <& /elements/header.html',
+       {
+         'title'   => 'Title',
+         'menubar' => \@menubar,
+         'etc'     => '', #included in <BODY> tag, for things like onLoad=
+         'head'    => '', #included before closing </HEAD> tag
+         'nobr'    => 0,  #1 for no <BR><BR> after the title
+       }
+  &>
+
+  %#old-style
+  <& /elements/header.html, 'Title', $menubar, $etc, $head &>
 
 </%doc>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
@@ -41,13 +41,6 @@ Example:
 
     <% include('init_overlib.html') |n %>
     <% include('rs_init_object.html') |n %>
-    <% include('logout.html') |n %>
-%   my $timeout =  $conf->config('logout-timeout');
-%   if ( $timeout && $timeout =~ /^\s*\d+\s*$/ ) {
-      <script type="text/javascript">
-        setTimeout('logout()', <% 60000 * $timeout %>);
-      </script>
-%   }
 
     <% $head |n %>
 
@@ -59,7 +52,7 @@ Example:
         <td align=left BGCOLOR="#ffffff"> <!-- valign="top" -->
           <font size=6><% $company_name || 'ExampleCo' %></font>
         </td>
-        <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b> <FONT SIZE="-2"><a href="javascript:void(0);" onClick="logout();">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
+        <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b> <FONT SIZE="-2"><a href="<%$fsurl%>loginout/logout.html">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
 %         if ( $conf->config("ticket_system")
 %              && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) {
             | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a>
diff --git a/httemplate/elements/logout.html b/httemplate/elements/logout.html
deleted file mode 100644 (file)
index 313dbfa..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<%doc>
-
-Example:
-
-  include( '/elements/logout.html');
-  This is the <a href="javascript:void()" onClick="logout();">logout</a> link.
-
-</%doc>
-<SCRIPT TYPE="text/javascript">
-
-  function logout() {
-    // count args; build URL
-    var url = "<% $fsurl. 'loginout/logout.html' %>";
-
-    var xmlhttp = rs_init_object();
-    xmlhttp.open("GET", url, false, "magic", "notyet");
-    xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
-    xmlhttp.send(null);
-
-    if (xmlhttp.readyState != 4) {
-      alert("Logout failed: readyState is " + xmlhttp.readyState);
-      return;
-    }
-
-    if (xmlhttp.status != 200) {
-      alert("Logout failed: status is " + xmlhttp.status);
-    } else {
-      var data = xmlhttp.responseText;
-      // alert('received response: ' + data);
-      if ( data.indexOf("<b>System error</b>") > -1 ) {
-        var w;
-        if ( w = window.open("about:blank") ) {
-          w.document.write(data);
-        } else {
-          // popup blocking?  should use an overlib popup instead 
-          alert("Error popup disabled; try disabling popup blocking to see");
-        }
-      } else {
-        window.location = "<% $fsurl. 'loginout/logout.html' %>";
-      }
-    }
-  }
-
-</SCRIPT>
diff --git a/httemplate/loginout/login.html b/httemplate/loginout/login.html
new file mode 100644 (file)
index 0000000..e5b4589
--- /dev/null
@@ -0,0 +1,68 @@
+<& /elements/header-minimal.html, 'Login' &>
+<link href="<%$url_string%>elements/freeside.css" type="text/css" rel="stylesheet">
+
+<CENTER>
+
+  <BR>
+  <FONT SIZE=5>Login</FONT>
+  <BR><BR>
+
+% if ( $error ) { 
+  <FONT SIZE="+1" COLOR="#ff0000"><% $error |h %></FONT>
+  <BR><BR>
+% } 
+             
+%#  <FORM METHOD="POST" ACTION="<%$url_string%>loginout/login">
+  <FORM METHOD="POST" ACTION="/login">
+    <INPUT TYPE="hidden" NAME="destination" VALUE="<% $r->prev->uri %>">
+
+    <TABLE CELLSPACING=0 CELLPADDING=4 BGCOLOR="#cccccc">
+      <TR>
+        <TD ALIGN="right">Username: </TD>
+        <TD><INPUT TYPE="text" NAME="credential_0" SIZE="13"></TD>
+      </TR>
+      <TR>
+        <TD ALIGN="right">Password: </TD>
+        <TD><INPUT TYPE="password" NAME="credential_1" SIZE="13"></TD>
+      </TR>
+    </TABLE>
+    <BR>
+    <INPUT TYPE="submit" VALUE="Login">
+
+  </FORM>
+
+</CENTER>
+
+</BODY></HTML>
+<%init>
+
+my %error = (
+  'no_cookie'       => '', #First login, don't display an error
+  'bad_cookie'      => 'Bad Cookie', #timed out?  server reboot?
+  'bad_credentials' => 'Incorrect username / password',
+  'logout'          => 'You have been logged out.',
+);
+
+my $url_string = CGI->new->url;
+
+my $error = $cgi->param('logout') || $r->prev->subprocess_env("AuthCookieReason");
+$error = exists($error{$error}) ? $error{$error} : $error;
+
+#fake a freeside path for /login so we get our .css.  shrug
+$url_string =~ s/login$/freeside\/login/ unless $url_string =~ /freeside\//;
+
+#even though this is kludgy and false laziness w/CGI.pm
+  $url_string =~ s{ / index\.html /? $ }
+                  {/}x;
+  $url_string =~
+    s{
+       /(login|loginout)
+       ([\w\-\.\/]*)
+       $
+     }
+     {}ix;
+
+  $url_string .= '/' unless $url_string =~ /\/$/;
+
+</%init>
index d8e1c63..33b87fe 100644 (file)
@@ -1,18 +1,10 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<HTML>
-  <HEAD>
-    <TITLE>
-      Logout page 
-    </TITLE>
-  </HEAD>
-  <BODY>
-    <BR><BR>
-    <CENTER>
-      You have logged out.
-    </CENTER>
-    <BR><BR>
-    <CENTER>
-      You can <a href="..">log in</a> again.
-    </CENTER>
-  </BODY>
-</HTML>
+<% $cgi->redirect($fsurl.'?logout=logout') %>
+<%init>
+
+my $auth_type = $r->auth_type;
+
+# Delete the cookie, etc.
+$auth_type->logout($r);
+#XXX etc: should delete the server-side session
+
+</%init>