Index: test/unit/ClientTests.pm
===================================================================
--- test/unit/ClientTests.pm	(revision 0)
+++ test/unit/ClientTests.pm	(revision 0)
@@ -0,0 +1,140 @@
+use strict;
+
+package ClientTests;
+
+# This is woefully incomplete, but it does at least check that
+# Client.pm compiles okay.
+
+use base qw(TWikiTestCase);
+BEGIN {
+    unshift @INC, '../../bin';
+    require 'setlib.cfg';
+};
+
+use CGI;
+use Error qw( :try );
+
+use TWiki;
+use TWiki::Client;
+use TWiki::UI::View;
+use TWiki::UI::Edit;
+
+my $session;
+my $twikiRegistrationAgent = 'TWikiRegistrationAgent';
+my ( $joe, $agent, $userTopic );
+
+sub new {
+    my $this = shift()->SUPER::new(@_);
+    return $this;
+}
+
+sub list_tests {
+    my $this = shift;
+    my @set = $this->SUPER::list_tests();
+
+    my $clz = new Devel::Symdump(qw(ClientTests));
+    for my $i ($clz->functions()) {
+        next unless $i =~ /::verify_/;
+        foreach my $impl qw( TemplateLogin ApacheLogin NoLogin) {
+            my $fn = $i;
+            $fn =~ s/\W/_/g;
+            my $sfn = 'ClientTests::test_'.$fn.$impl;
+            no strict 'refs';
+            *$sfn = sub {
+                my $this = shift;
+                $TWiki::cfg{LoginManager} = 'TWiki::Client::'.$impl;
+                &$i($this);
+            };
+            use strict 'refs';
+            push(@set, $sfn);
+        }
+    }
+    return @set;
+}
+
+sub set_up {
+    my $this = shift;
+    $this->SUPER::set_up();
+
+    $session = new TWiki();
+
+    $TWiki::cfg{UseClientSessions} = 1;
+    $TWiki::cfg{PasswordManager} = "TWiki::Users::HtPasswdUser";
+    $TWiki::cfg{Htpasswd}{FileName} = "/tmp/htpasswd";
+    $TWiki::cfg{AuthScripts} = "edit";
+
+    # $this->setup_user();
+}
+
+sub set_up_user {
+    $joe = $session->{users}->findUser( "joe", "Main.JoeDoe" );
+    $agent = $session->{users}->findUser( $twikiRegistrationAgent,
+                                          $twikiRegistrationAgent);
+    $userTopic = 
+      $session->{users}->addUserToTWikiUsersTopic( $joe, $agent);
+}
+
+sub tear_down {
+    my $this = shift;
+    $this->SUPER::tear_down();
+}
+
+sub testClient {
+    my $this = shift;
+
+    # make sure it compiles, at least.
+    $this->assert($TWiki::Client::VERSION >= 1.0, "version");
+}
+
+sub capture {
+    my $this = shift;
+    my( $proc, $session ) = @_;
+    $session->{client}->checkAccess();
+    $this->SUPER::capture( @_ );
+}
+
+sub verify_edit {
+    my $this = shift;
+    my ( $query, $text );
+
+    $query = new CGI ({});
+    $query->path_info( "/Main/WebHome" );
+    $ENV{SCRIPT_NAME} = "view";
+    $session = new TWiki( undef, $query );
+    try {
+        $text = $this->capture( \&TWiki::UI::View::view, $session );
+    } catch TWiki::OopsException with {
+        $this->assert(0,shift->stringify());
+    } catch Error::Simple with {
+        $this->assert(0,shift->stringify());
+    };
+
+    $query = new CGI ({});
+    $query->path_info( "/Main/WebHome" );
+    $ENV{SCRIPT_NAME} = "edit";
+    $session = new TWiki( undef, $query );
+
+    try {
+        $text = $this->capture( \&TWiki::UI::Edit::edit, $session );
+    } catch TWiki::AccessControlException with {
+    } catch Error::Simple with {
+        $this->assert(0,shift->stringify());
+    } otherwise {
+        $this->assert(0, "expected an oops redirect");
+    };
+
+    $query = new CGI ({});
+    $query->path_info( "/Main/WebHome" );
+    $ENV{SCRIPT_NAME} = "edit";
+    $session = new TWiki( "joe", $query );
+
+    try {
+        $text = $this->capture( \&TWiki::UI::Edit::edit, $session );
+    } catch TWiki::OopsException with {
+        $this->assert(0,shift->stringify());
+    } catch Error::Simple with {
+        $this->assert(0,shift->stringify());
+    };
+}
+
+1;
Index: lib/TWiki.pm
===================================================================
--- lib/TWiki.pm	(revision 4649)
+++ lib/TWiki.pm	(working copy)
@@ -424,6 +424,7 @@
 use TWiki::Access;    # access control
 use TWiki::Attach;    # file attachments
 use TWiki::Attrs;     # tag attribute handling
+use TWiki::Client;    # client session handling
 use TWiki::Form;      # forms
 use TWiki::Net;       # SMTP, get URL
 use TWiki::Plugins;   # plugins handler
@@ -626,7 +627,11 @@
     $hopts->{'Content-Type'} = $contentType;
 
     # New (since 1.026)
-    $this->{plugins}->modifyHeaderHandler($hopts);
+    $this->{plugins}->modifyHeaderHandler( $hopts );
+
+    # add cookie(s)
+    $this->{client}->modifyHeader( $hopts );
+
     my $hdr = CGI::header( $hopts );
 
     print $hdr;
@@ -660,6 +665,7 @@
         if ( $query && $query->param( 'noredirect' )) {
             my $content = join(' ', @_) . " \n";
             $this->writeCompletePage( $query, $content );
+        } elsif ( $this->{client}->redirectCgiQuery( $query, $url ) ) {
         } elsif ( $query ) {
             print $query->redirect( $url );
         }
@@ -971,6 +977,7 @@
     $this->{search} = new TWiki::Search( $this );
     $this->{templates} = new TWiki::Templates( $this );
     $this->{attach} = new TWiki::Attach( $this );
+    $this->{client} = new TWiki::Client( $this );
     # cache CGI information in the session object
     $this->{cgiQuery} = $query;
     $this->{remoteUser} = $remoteUser;
@@ -1088,12 +1095,18 @@
     } else {
         $this->{urlHost} = $TWiki::cfg{DefaultUrlHost};
     }
+
+    # setup the cgi session, from a cookie or the url. this may return
+    # the login, but even if it does, plugins will get the chance to override
+    # it below.
+    my $login = $this->{client}->load();
+
     # initialize preferences, first part for site and web level
     $this->{prefs} = new TWiki::Prefs( $this );
 
     # SMELL: there should be a way for the plugin to specify
     # the WikiName of the user as well as the login.
-    my $login = $this->{plugins}->load( $TWiki::cfg{DisableAllPlugins} );
+    $login = $this->{plugins}->load( $TWiki::cfg{DisableAllPlugins} ) || $login;
     unless( $login ) {
         $login = $this->{users}->initializeRemoteUser( $remoteUser );
     }
@@ -1138,6 +1151,21 @@
 
 =pod
 
+---++ ObjectMethod finish
+Complete processing after the client's HTTP request has been responded
+to. Right now this only entails one activity: calling TWiki::Client to
+flushing the user's
+session (if any) to disk.
+
+=cut
+
+sub finish {
+    my $this = shift;
+    $this->{client}->finish();
+}
+
+=pod
+
 ---++ ObjectMethod writeLog (  $action, $webTopic, $extra, $user  )
    * =$action= - what happened, e.g. view, save, rename
    * =$wbTopic= - what it happened to
@@ -2073,7 +2101,7 @@
 
 =pod
 
----++ StaticMethod registerTagHandler( $fnref )
+---++ StaticMethod registerTagHandler( $tag, $fnref )
 
 STATIC Add a tag handler to the function tag handlers.
    * =$tag= name of the tag e.g. MYTAG
Index: lib/TWiki.cfg
===================================================================
--- lib/TWiki.cfg	(revision 4649)
+++ lib/TWiki.cfg	(working copy)
@@ -105,8 +105,8 @@
 $cfg{RemoteUserFileName} = "$cfg{DataDir}/remoteusers.txt";
 
 # **SELECT TWiki::Users::HtPasswdUser,TWiki::Users::NoPasswdUser**
-# TWiki doesn't do its own authentication, but instead works closely
-# with the web server that does the authentication. The following
+# TWiki can do its own authentication or work closely
+# with the web server to do it. The following
 # settings are used in the TWiki registration scripts to help maintain
 # users and passwords in the web server's authentication environment.
 # Name of the password handler implementation. TWiki ships with two
@@ -122,6 +122,23 @@
 # to your package from here.
 $cfg{PasswordManager} = 'TWiki::Users::HtPasswdUser';
 
+# **SELECT TWiki::Client::ApacheLogin,TWiki::Client::TemplateLogin,TWiki::Client::NoLogin**
+# TWiki supports different ways of responding when the user asks to log
+# in (or is required to log in). They are:
+# <ol><li>
+# TWiki::Client::TemplateLogin - Redirect to the login template, which
+#   asks for a username and password in a form instead of in a browser-specific
+#   dialog box. This is the same behavior as AuthPagePlugin.
+# </li><li>
+# TWiki::Client::ApacheLogin - Redirect to an '...auth' script for which
+#   Apache can be configured to ask for authorization information.
+# </li><li>
+# TWiki::Client::NoLogin - Don't support logging in.
+# </li></ol>
+# You can provide your own alternative by implementing the authenticate method
+# in a new package, and pointing to your package from here.
+$cfg{LoginManager} = 'TWiki::Client::NoLogin';
+
 # **PATH**
 # Path to the file that stores passwords, for TWiki::Users::HtPasswdUser.
 # You can use the <tt>htpasswd</tt> Apache program to create a new
@@ -169,6 +186,26 @@
 # use TWiki to manually rename the existing topic</strong>
 $cfg{UsersTopicName} = 'TWikiUsers';
 
+# **BOOLEAN**
+# Use persistent CGI session tracking?
+$cfg{UseClientSessions} = 1;
+
+# **STRING 20**
+# Name of the "sticky skin" client session variable
+$cfg{StickSkinVar} = "stickskin";
+
+# **STRING 20**
+# Value for StickSkinVar that resets the user's skin preference to
+# the TWiki default.
+$cfg{StickSkinOffValue} = "default";
+
+# **STRING 80**
+# Regular expression to describe which scripts require the user to authenticate.
+# If you're doing authentication, a good value is <tt>"attach, edit, manage,
+# passwd, rename, resetpasswd, save, upload, viewauth, rdiffauth"</tt>. If
+# you don't want to require authentication, then leave this blank.
+$cfg{AuthScripts} = "";
+
 # **PATH**
 # Path control. If set, overrides the default PATH setting to control
 # where TWiki looks for programs. Check notes for your operating
Index: lib/TWiki/UI.pm
===================================================================
--- lib/TWiki/UI.pm	(revision 4649)
+++ lib/TWiki/UI.pm	(working copy)
@@ -65,7 +65,6 @@
     if( $ENV{'GATEWAY_INTERFACE'} ) {
         # script is called by browser
         $query = new CGI;
-        $user = $query->remote_user();
     } else {
         # script is called by cron job or user
         $scripted = 1;
@@ -104,56 +103,21 @@
     # end of comment out in production version
 
     try {
+        $session->{client}->checkAccess();
         &$method( $session );
     } catch TWiki::AccessControlException with {
         my $e = shift;
-        # Had an access control violation. See if there is an 'auth' version
-        # of this script, may be a result of not being logged in.
-        my $url;
-        $script =~ s/^(.*\/)([^\/]+)($TWiki::cfg{ScriptSuffix})?$/$1/;
-        my $scriptPath = $1;
-        my $scriptName = $2;
-        $script .= "$scriptPath${scriptName}auth$TWiki::cfg{ScriptSuffix}";
-        if( ! $query->remote_user() && -e $script ) {
-            $url = $ENV{REQUEST_URI};
-            if( $url && $url =~ s/\/$scriptName/\/${scriptName}auth/ ) {
-                # $url i.e. is "twiki/bin/view.cgi/Web/Topic?cms1=val1&cmd2=val2"
-                $url = $session->{urlHost}.$url;
-            } else {
-                # If REQUEST_URI is rewritten and does not contain the script
-                # name, try looking at the CGI environment variable
-                # SCRIPT_NAME.
-                #
-                # Assemble the new URL using the host, the changed script name,
-                # the path info, and the query string.  All three query
-                # variables are in the list of the canonical request meta
-                # variables in CGI 1.1.
-                $scriptPath      = $ENV{'SCRIPT_NAME'};
-                my $pathInfo    = $ENV{'PATH_INFO'};
-                my $queryString = $ENV{'QUERY_STRING'};
-                $pathInfo    = '/' . $pathInfo    if ($pathInfo);
-                $queryString = '?' . $queryString if ($queryString);
-                if( $scriptPath && $scriptPath =~ s/\/$scriptName/\/${scriptName}auth/ ) {
-                    $url = $session->{urlHost}.$scriptPath;
-                } else {
-                    # If SCRIPT_NAME does not contain the script name
-                    # the last hope is to try building up the URL using
-                    # the SCRIPT_FILENAME.
-                    $url = $session->{urlhost}.$session->{scriptUrlPath}.'/'.
-                      ${scriptName}.$TWiki::cfg{ScriptSuffix};
-                }
-                $url .= $pathInfo.$queryString;
-            }
-            $session->redirect( $url );
+        if( $session->{client}->authenticate() ) {
+            # okay
         } else {
-            $url = $session->getOopsUrl( 'accessdenied',
+            my $url = $session->getOopsUrl( 'accessdenied',
                                          def => 'topic_access',
                                          web => $e->{web},
                                          topic => $e->{topic},
                                          params => [ $e->{mode},
                                                      $e->{reason} ] );
+            $session->redirect( $url );
         }
-        $session->redirect( $url );
 
     } catch TWiki::OopsException with {
         my $e = shift;
@@ -174,6 +138,8 @@
         print "Content-type: text/plain\n\n";
         print $e->stringify();
     };
+
+    $session->finish();
 }
 
 =pod twiki
Index: lib/TWiki/Client.pm
===================================================================
--- lib/TWiki/Client.pm	(revision 0)
+++ lib/TWiki/Client.pm	(revision 0)
@@ -0,0 +1,775 @@
+# TWiki Enterprise Collaboration Platform, http://TWiki.org/
+#
+# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com
+# and TWiki Contributors. All Rights Reserved. TWiki Contributors
+# are listed in the AUTHORS file in the root of this distribution.
+# NOTE: Please extend that file, not this notice.
+#
+# Additional copyrights apply to some or all of the code in this
+# file as follows:
+# Copyright (C) 2000-2003 Andrea Sterbini, a.sterbini@flashnet.it
+# Copyright (C) 2005 Garage Games
+# Copyright (C) 2005 Crawford Currie http://c-dot.co.uk
+# Copyright (C) 2005 Greg Abbas, twiki@abbas.org
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version. For
+# more details read LICENSE in the root of this distribution.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# As per the GPL, removal of this notice is prohibited.
+
+=pod
+
+---+ package TWiki::Client
+This module defines the object that handles Client session
+management. It's based heavily on SessionPlugin & AuthPagePlugin.
+
+=cut
+
+package TWiki::Client;
+
+use strict;
+use Assert;
+use Error qw( :try );
+
+# if you want to turn on ip matching, change the "use CGI::Session"
+# statement below to "use CGI::Session qw/-ip_match/".
+#
+# ip matching used to be the recommended approach, but it isn't anymore
+# because it has a bunch of drawbacks. specifically:
+#   * it doesn't help much if clients are behind a NAT firewall
+#   * it doesn't help much because if a hacker can steal a session id, they
+#     can steal your password too
+#   * it breaks clients that get their IP addresses reassigned by DHCP
+#   * it breaks clients that change their apparent IP address (e.g. VPN)
+use CGI::Session;
+use CGI::Cookie;
+
+BEGIN {
+    # suppress stupid warning in CGI::Cookie
+    if ( exists $ENV{MOD_PERL} ) {
+        if ( !defined( $ENV{MOD_PERL_API_VERSION} )) {
+            $ENV{MOD_PERL_API_VERSION} = 1;
+        }
+    }
+}
+
+=pod
+
+---++ ClassMethod new( $session )
+
+Construct new client object.
+
+=cut
+
+sub new {
+    my ( $class, $session ) = @_;
+    my $this = bless( {}, $class );
+    ASSERT($session->isa( 'TWiki')) if DEBUG;
+    $this->{twiki} = $session;
+
+    $this->{cookies} = [];
+    @{$this->{authScripts}} = split( /[\s,]+/, $TWiki::cfg{AuthScripts} );
+
+    return $this;
+}
+
+=pod
+
+---++ ObjectMethod load()
+
+Get the client session data, using the cookie and/or the request URL.
+
+=cut
+
+sub load {
+    my $this = shift;
+
+    return unless( $TWiki::cfg{UseClientSessions} );
+
+    eval "use $TWiki::cfg{LoginManager}";
+    throw Error::Simple( 'Login Manager: '.$@) if $@;
+
+    my $query = $this->{twiki}->{cgiQuery};
+
+    my $twiki = $this->{twiki};
+
+    # Initialize the session (you may wish to change this directory,
+    # but /tmp is probably best)
+    #
+    # Borrowing from the previous version of TWiki, perhaps using:
+    #
+    #   TWiki::Func::getDataDir() . '/.session'
+    #
+    # would work well for you. Just be sure to create data/.session
+    # and make it writable by the webserver.
+    #
+    # Another experiment might be to change your serializer. Storable
+    # is a good option. See CGI::Session on http://search.cpan.org/
+    # for more information on adding ';initializer:Storable' after the
+    # 'driver:File' below (other serializers are available as well).
+
+    $this->{haveCookie} = defined($query->raw_cookie( $CGI::Session::NAME ));
+
+    my $cgisession = new CGI::Session( 'driver:File', $query,
+                                       { Directory=>'/tmp' } );
+
+    $this->{cgisession} = $cgisession;
+
+    my $sessionId = $cgisession->id();
+    $this->{sessionId} = $sessionId;
+
+    my $guest = $TWiki::cfg{DefaultUserLogin};
+
+    # For added security, every time a user logs in and gives
+    # us a user to check, verify that the user we're
+    # about to flush to the session file is the same as the
+    # user already stored in the session file.
+    #
+    # If there is another valid username stored in the session file,
+    # then someone has somehow just borrowed a session ID from someone
+    # else. To prevent further havoc, clear this session ID (perhaps
+    # in the future it'd be better just to dispatch a new session ID
+    # to this user; however, if they already have the session ID of
+    # another user, it's probably best to get rid of it since it has
+    # been compromised).
+    #
+    $TWiki::cfg{LoginManager}->checkSession( $this );
+
+    # See whether the user was logged in (first webserver, then
+    # session, then default)
+    my $authUser = $TWiki::cfg{LoginManager}->getUser( $this );
+    $authUser ||= $cgisession->param( 'AUTHUSER' );
+
+    # if we couldn't get the login manager or the http session to tell
+    # us who the user is, then let's use the CGI "remote user"
+    # variable (which may have been set manually by a unit test,
+    # or it might have come from Apache).
+    $authUser ||= $twiki->{remoteUser}; # $guest
+
+    #if ( $ENV{'REDIRECT_STATUS'} eq '401' ) {
+    #    # invalidating session due to 401 status
+    #	 $cgisession->clear();
+    #    return 1;
+    #}
+
+    # Save the user's information again if they do not appear to be a guest
+    my $sessionIsAuthenticated = ( $authUser ne $guest );
+
+    my $do_logout = defined( $query ) && $query->param( 'logout' );
+    if( $do_logout ) {
+        $sessionIsAuthenticated = 0;
+        $authUser = undef;
+    }
+    if( ( $do_logout || $sessionIsAuthenticated )) {
+        $cgisession->param( 'AUTHUSER', $authUser );
+        $cgisession->flush();
+    }
+    if( $do_logout ) {
+        my $origurl = $query->url() . $query->path_info();
+        #my $url = $twiki->getScriptUrl( $web, $topic, '' ).
+        #  '?origurl='.$origurl;
+        $this->redirectCgiQuery( $query, $origurl );
+
+        # mod_perl is okay with calling exit (it patches it) but
+        # the unit tests aren't. so doing a "redirect abort" should
+        # probably terminate processing using an exception.
+        # exit 0;
+    }
+
+    # SMELL: $TWiki::cfg{UseTransSessionId} is not set in TWiki.cfg,
+    # and it isn't clear what it should be set to even if it is.
+    # $useTransSID sets whether or not to use
+    # transparent CGI session IDs. If cookies are working, turn
+    # this off. Otherwise, set it to whatever the user set in
+    # $useTransSessionId. Still report to the user though that
+    # %USE_TRANS_SESSIONID% is set to $useTransSessionId
+    my $useTransSID = (defined($query) &&
+                         $query->cookie( $CGI::Session::NAME ))
+      ? 0 : $TWiki::cfg{UseTransSessionId};
+
+    # Save our state to member variables, because we'll need them later.
+    $this->{authUser} = $authUser;
+    $this->{sessionIsAuthenticated} = $sessionIsAuthenticated;
+    $this->{useTransSID} = $useTransSID;
+
+    # register tag handlers and values
+    TWiki::registerTagHandler('SESSIONLOGONURL', \&_SESSIONLOGONURL );
+    TWiki::registerTagHandler('SESSIONLOGONURLPATH', \&_SESSIONLOGONURLPATH );
+    TWiki::registerTagHandler('LOGIN', \&_LOGIN );
+    TWiki::registerTagHandler('LOGOUT',\&_LOGOUT );
+    TWiki::registerTagHandler('SESSION_VARIABLE', \&_SESSION_VARIABLE );
+    TWiki::registerTagHandler('AUTHENTICATED', \&_AUTHENTICATED );
+
+    $twiki->{SESSION_TAGS}{SESSIONID} = ( $sessionId || '');
+    $twiki->{SESSION_TAGS}{SESSIONVAR} = ( $CGI::Session::NAME || '');
+    $twiki->{SESSION_TAGS}{SESSION_IS_AUTHENTICATED} =
+      ( $sessionIsAuthenticated || '');
+    $twiki->{SESSION_TAGS}{STICKSKIN} =
+      ( defined($query) && $query->param( $TWiki::cfg{StickSkinVar} )) || '';
+    $twiki->{SESSION_TAGS}{AUTHUSER_SESSIONVAR} = 'AUTHUSER';
+    $twiki->{SESSION_TAGS}{DO_SESSION_IP_MATCHING} =
+      ( $CGI::Session::IP_MATCH ? 1 : 0 );
+    $twiki->{SESSION_TAGS}{USE_TRANS_SESSIONID} = ( $useTransSID || '');
+    $twiki->{SESSION_TAGS}{STICKSKINVAR} =
+      ( $TWiki::cfg{StickSkinVar} || '' );
+    $twiki->{SESSION_TAGS}{STICKSKINOFFVALUE} =
+      ( $TWiki::cfg{StickSkinOffValue} || '' );
+
+    return $authUser;
+}
+
+=pod
+
+---++ ObjectMethod checkAccess
+
+=cut
+
+sub checkAccess {
+
+    return unless( $TWiki::cfg{UseClientSessions} );
+
+    my $this = shift;
+
+    if( !$this->{sessionIsAuthenticated} ) {
+        my $script = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'};
+        $script =~ s@^.*/([^/]+)@$1@g;
+
+        if( defined $script) {
+            my $found = 0;
+            for (@{$this->{authScripts}}) {
+                $found = 1 if( $script eq $_ );
+            }
+
+            if( $found ) {
+                my $topic = $this->{twiki}->{topicName};
+                my $web = $this->{twiki}->{webName};
+                throw TWiki::AccessControlException(
+                    $script, $this->{twiki}->{user}, $web, $topic,
+                    "authorization required");
+            }
+        }
+    }
+}
+
+=pod
+
+---++ ObjectMethod finish
+Complete processing after the client's HTTP request has been responded
+to. Flush the user's session (if any) to disk.
+
+=cut
+
+sub finish {
+    return unless( $TWiki::cfg{UseClientSessions} );
+    my $this = shift;
+    my $cgisession = $this->{cgisession};
+
+    # this predicate used to be
+    # $this->{sessionIsAuthenticated} && defined($cgisession),
+    # but that had the problem that sometimes an unauthenticated version
+    # of the session would overwrite the more recent authenticated version
+    # on disk. that's because with mod_perl, an unflushed session object
+    # would sometimes hang around. then when the apache server was
+    # terminated, it would get flushed. this way, if we didn't get a
+    # cookie then we'll tell the session manager that we don't want
+    # it to _ever_ flush the session.
+    if($this->{haveCookie}) {
+        $cgisession->flush();
+    } else {
+        # this is drastic and not really necessary, but unfortunately
+        # CGI::Session makes it impossible for us to say "don't
+        # _bother_ writing it to disk if you haven't already". :-(
+        $cgisession->delete();
+    }
+}
+
+=pod
+
+---++ ObjectMethod userLoggedIn()
+
+Call this when the user logs in. It's invoked from TWiki::UI::Register::finish
+for instance, when the user follows the link in their verification email
+message.
+
+=cut
+
+sub userLoggedIn {
+    my ( $this, $authUser, $wikiName ) = @_;
+
+    my $cgisession = $this->{cgisession};
+    my $sessionIsAuthenticated = defined($authUser) ? 1 : 0;
+
+    if( $TWiki::cfg{DefaultUserLogin} ne $authUser ) {
+        $cgisession->param( 'AUTHUSER', $authUser );
+        $cgisession->flush();
+    }
+
+    $this->{authUser} = $authUser;
+    $this->{sessionIsAuthenticated} = $sessionIsAuthenticated;
+}
+
+=pod
+
+---++ ObjectMethod endRenderingHandler()
+SMELL: this method uses the plugins endRenderingHandler method which is
+deprecated, and stunningly inefficient. It badly needs to be refactored.
+
+=cut
+
+sub endRenderingHandler {
+    return unless( $TWiki::cfg{UseClientSessions} );
+
+    my $this = shift;
+
+    my $useTransSID = $this->{useTransSID};
+    my $sessionId = $this->{sessionId};
+
+    # This handler is called by getRenderedVersion just after the line loop, that is,
+    # after almost all XHTML rendering of a topic. <nop> tags are removed after this.
+
+    # If cookies are not turned on and transparent CGI session IDs are,
+    # grab every URL that is an internal link and pass a CGI variable 
+    # with the session ID
+    if( $useTransSID ) {
+        # Internal links are specified by forms, hrefs, or onclicks that either
+        # point to a link with no colons in it or links that match links that
+        # would bve returned by getScriptUrl. Internal links are additionally
+        # specified by forms that have no target.
+
+        # Gather the URLs one would expect to be returned by getScriptUrl if a URL
+        # was inside of quotes (A) or outside of quotes (B) or inside of single quotes 
+        # for javascript (C).
+        #
+        # Use these later in all the regex's below.
+        my $myScriptUrlA = quotemeta($this->{twiki}->getScriptUrl( "ZZZZ", "ZZZZ", "ZZZZ" ));
+        my $myScriptUrlB = $myScriptUrlA;
+        my $myScriptUrlC = $myScriptUrlA;
+        $myScriptUrlA =~ s/ZZZZ/[^"#]*?/g;
+        $myScriptUrlB =~ s/ZZZZ/[^\\s#>]*?/g;
+        $myScriptUrlC =~ s/ZZZZ/[^'#>]*?/g;
+
+        #
+        # NOTE: Lots of the defined's here are to quiet down the highly overrated perl -w
+        #
+
+        # Catch hyperlinks with targets containing no colon
+        $_[0] =~ s/(<a\s[^>]*?(?<=\s)href=)(?:(")([^:]*?)([#"])|([^:]*?(?=[#\s>])))/@{[ defined($5) ? "$1$5" : "$1$2$3" ]}@{[ ( (defined($3) && ($3=~m!\?!))||(defined($5) && ($5=~m!\?!)) ) ? "&" : "?" ]}$CGI::Session::NAME=$sessionId@{[defined($4) ? "$4" : ""]}/goi;
+
+        # Catch hyperlinks with targets that could be returned by getScriptUrl
+        $_[0] =~ s/(<a\s[^>]*?(?<=\s)href=)(?:(")((?-i:$myScriptUrlA[^"#]*?))([#"])|((?-i:$myScriptUrlB[^\s#>]*?).*?(?=[#\s>])))/@{[ defined($5) ? "$1$5" : "$1$2$3"]}@{[( (defined($3) && ($3=~m!\?!))||(defined($5) && ($5=~m!\?!)) )? "&" : "?" ]}$CGI::Session::NAME=$sessionId@{[ defined($4) ? "$4" : ""]}/goi;
+
+        # Catch onclicks that trigger changes of location.href to targets with no colon
+        $_[0] =~ s/(<[^>]*?\sonclick=(?:"[^"]*?|)(?=(?:javascript:|))location\.href=)(')([^:]*?)([#'])/$1$2$3@{[ ($3=~m!\?!) ? "&" :"?" ]}$CGI::Session::NAME=$sessionId$4/goi;
+
+        # Catch onclicks that trigger changes of location.href to targets that could be returned by getScriptUrl
+        $_[0] =~ s/(<[^>]*?\sonclick=(?:"[^"]*?|)(?=(?:javascript:|))location\.href=)(')((?-i:$myScriptUrlC[^'#]*?))([#'])/$1$2$3@{[ ($3=~m!\?!) ? "&" : "?" ]}$CGI::Session::NAME=$sessionId$4/goi;
+
+
+        # Catch all FORM elements and add a hidden Session ID variable 
+        #
+        # Only do this if the form is pointing to an internal link. This occurs if there are no
+        # colons in its target, if it has no target, or if its target matches a getScriptUrl URL.
+        #
+        $_[0] =~ s%(<form[^>]*?>)%@{ [ "$1" . ( ( $1 =~ /^<form(?:(?!.*?\saction=).*?>|\s.*?(?<=\s)action=(?:"(?:[^:]*?|(?-i:$myScriptUrlA))"|(?:[^:"\s]*?|(?-i:$myScriptUrlB))(?:\s|>)))/ ) ? "\n<input type=\"hidden\" name=\"$CGI::Session::NAME\" value=\"$sessionId\" />" : "") ] }%gio;
+
+    }
+
+    # And, finally, the logon stuff
+    # this MUST render after TigerSkinPlugin commonTagsHandler does TIGERLOGON
+    $_[0] =~ s/%SESSIONLOGON%/$this->_dispLogon()/geo;
+    $_[0] =~ s/%SKINSELECT%/$this->_skinSelect()/geo;
+
+}
+
+=pod
+
+---++ ObjectMethod modifyHeader( $hdr )
+
+=cut
+
+sub modifyHeader {
+    return unless( $TWiki::cfg{UseClientSessions} );
+
+    my( $this, $hopts ) = @_;
+
+    my $query = $this->{twiki}->{cgiQuery};
+
+    my $c = new CGI::Cookie( -name => $CGI::Session::NAME,
+                             -value => $this->{cgisession}->id,
+                             -path => '/' );
+
+    my @cs = @{$this->{cookies}};
+    push @cs, $c;
+    $hopts->{cookie} = \@cs;
+}
+
+=pod
+
+---++ ObjectMethod redirectCgiQuery( $url )
+
+=cut
+
+sub redirectCgiQuery {
+    return 0 unless $TWiki::cfg{UseClientSessions};
+
+    my( $this, $query, $url ) = @_;
+
+    my $sessionId = $this->{sessionId};
+    my $useTransSID = $this->{useTransSID};
+    my $cgisession = $this->{cgisession};
+
+    if( $useTransSID && $url !~ m/\?$CGI::Session::NAME=/ ) {
+        # If the URL has no colon in it, it must be an internal URL
+        if( $url !~ /:/ ) {
+            # Does it already have CGI parameters passed?
+            if( $url =~ m/\?/ ) {
+                my @urlparts = split( $url, /\?/, 2 );
+                $url = $urlparts[0] . "?$CGI::Session::NAME=$sessionId&" . $urlparts[1];
+            }
+            # Does it have any anchors passed?
+            elsif( $url =~ m/#/ ) {
+                my @urlparts = split( $url, /#/, 2 );
+                $url = $urlparts[0] . "?$CGI::Session::NAME=$sessionId#" . $urlparts[1];
+            }
+            # Otherwise, we're the first CGI parameter
+            else {
+                $url .= "?$CGI::Session::NAME=$sessionId";
+            }
+
+        } else {
+            # It MAY be an external URL
+            # This could be better. This could be integrated into the above...
+            # This could use split instead of regex's... 
+
+            # Remember our scriptUrl form to match internal URLs that are referred
+            # to like external URLs
+            my $myScriptUrl = quotemeta($this->{twiki}->getScriptUrl(
+                "XXXX", "YYYY", "ZZZZ" ));
+            $myScriptUrl =~ s@XXXX@[^/]*?@g;
+            $myScriptUrl =~ s@YYYY@[^#\?/]*@g;
+            $myScriptUrl =~ s@ZZZZ@[^/]*?@g;
+
+            # If we start with our internal URL....
+            if( $url =~ /(^$myScriptUrl)/o ) {
+                # Only ask Perl to do that work once; save what we already have
+                my $theScript = $1;
+
+                # Are there other CGI parameters?
+                if( $url =~ /(?:^$theScript)(?:\?)(.*)/ ) {
+                    $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId . '&' . $1; 
+                }
+                # Are there any anchors?
+                elsif( $url =~ /(?:^$theScript)(#.*)/ ) {
+                    $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId . $1; 
+                }
+                # Otherwise, we're the first CGI parameter
+                else {
+                    $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId;
+                }
+            }
+        }
+    }
+
+    # This usually won't be important, but just in case they haven't
+    # yet received the cookie and happen to be redirecting, be sure
+    # they have the cookie. (this is a lot more important with
+    # transparent CGI session IDs, because the session DIES when those
+    # people go across a redirect without a ?CGISESSID= in it... But
+    # EVEN in that case, they should be redirecting to a URL that
+    # already *HAS* a sessionID in it... Maybe...)
+    #
+    # So this is just a big fat precaution, just like the rest of this
+    # whole handler.
+    my $cookie = new CGI::Cookie(-name=>$CGI::Session::NAME,
+                                 -value=>$cgisession->id,
+                                 -path=>'/');
+    my @cs = @{$this->{cookies}};
+    push @cs, $cookie;
+    print $query->redirect( -url=>$url, -cookie=>\@cs );
+
+    return 1;
+}
+
+=pod
+
+---++ ObjectMethod getSessionValue( $name ) -> $value
+Get the value of a session variable.
+
+=cut
+
+sub getSessionValue {
+    my( $this, $key ) = @_;
+    my $cgisession = $this->{cgisession};
+
+    return $cgisession->param( $key );
+}
+
+=pod
+
+---++ ObjectMethod setSessionValue( $name, $value )
+Set the value of a session variable.
+We do not allow setting of AUTHUSER.
+
+=cut
+
+sub setSessionValue {
+    my( $this, $key, $value ) = @_;
+    my $cgisession = $this->{cgisession};
+
+    # We do not allow setting of AUTHUSER.
+    if(( $key ne 'AUTHUSER' ) &&
+         defined( $cgisession->param( $key, $value ))) {
+        return 1;
+    }
+
+    return undef;
+}
+
+=pod
+
+---++ ObjectMethod clearSessionValue( $name )
+Clear the value of a session variable.
+We do not allow setting of AUTHUSER.
+
+=cut
+
+sub clearSessionValue {
+    my( $this,, $key ) = @_;
+    my $cgisession = $this->{cgisession};
+
+    # We do not allow clearing of AUTHUSER.
+    if( ( $key ne 'AUTHUSER' ) && 
+          defined( $cgisession->param( $key ))) {
+        $cgisession->clear( [ $_[1] ] );
+
+        return 1;
+    }
+
+    return undef;
+
+}
+
+=pod
+
+---++ ObjectMethod authenticate()
+
+If the user has an existing authenticated session, the function simply drops
+though allowing the calling script to complete. If no auth is in place it
+forces redirection to the "login" script, passing it the original URL,
+and does not return.
+
+=cut
+
+sub authenticate {
+    my $this = shift;
+    return $TWiki::cfg{LoginManager}->authenticate( $this );
+}
+
+=pod
+
+---++ ObjectMethod sessionLogonUrl()
+
+=cut
+
+sub sessionLogonUrl {
+    my $this = shift;
+    return $TWiki::cfg{LoginManager}->sessionLogonUrl( $this );
+}
+
+=pod
+
+---++ ObjectMethod sessionLogonUrlPath()
+
+=cut
+
+sub sessionLogonUrlPath {
+    my $this = shift;
+    return $TWiki::cfg{LoginManager}->sessionLogonUrlPath( $this );
+}
+
+=pod
+
+---++ ObjectMethod logon( $query, $session )
+
+Handler called from the "logon" script. This script is redirected to
+if there is no existing session cookie.
+If a login name and password have been passed in the query, it
+validates these and if authentic, redirects to the original
+script. If there is no username in the query or the username/password is
+invalid (validate returns non-zero) then it prompts again.
+
+=cut
+
+sub logon {
+    my( $this, $query, $twikiSession ) = @_;
+
+    my $origurl = $query->param( 'origurl' );
+    my $loginName = $query->param( 'username' );
+    my $loginPass = $query->param( 'password' );
+    my $banner = 'You are not logged in';
+    my $note = '';
+    my $cgisession = $this->{cgisession};
+    my $topic = $this->{twiki}->{topicName};
+    my $web = $this->{twiki}->{webName};
+
+    my $currUser = $cgisession->param( 'AUTHUSER' );
+    if( $currUser ) {
+        $banner = $currUser.' is currently logged in';
+        $note = 'Enter a new username and password to change identity';
+    }
+
+    if( $loginName ) {
+        my $passwordHandler = $this->{twiki}->{users}->{passwords};
+        my $validation = $passwordHandler->checkPassword( $loginName, $loginPass );
+        if( $validation ) {
+            $this->userLoggedIn( $loginName );
+            $cgisession->param( 'VALIDATION', $validation );
+            $banner = $loginName.' is logged in';
+            if( !$origurl || $origurl eq $query->url() ) {
+                $origurl = $this->{twiki}->getScriptUrl( $web, $topic, 'view' );
+            }
+            $this->redirectCgiQuery( $query, $origurl );
+            return;
+        } else {
+            $banner = 'Unrecognised user and/or password';
+        }
+    }
+
+    my $tmpl = $this->{twiki}->{templates}->readTemplate(
+        'login', $this->{twiki}->getSkin() );
+    # TODO: add JavaScript password encryption in the template
+    # to use a template)
+    $tmpl =~ s/%ORIGURL%/$origurl/g;
+    $tmpl =~ s/%BANNER%/$banner/g;
+    $tmpl =~ s/%NOTE%/$note/g;
+
+    $tmpl = $this->{twiki}->handleCommonTags( $tmpl, $web, $topic );
+    $tmpl = $this->{twiki}->{renderer}->getRenderedVersion( $tmpl, '' );
+    $this->{twiki}->writePageHeader( $query );
+    print $tmpl;
+}
+
+sub _LOGIN {
+    #($session, \%params, $web, $topic ) = @_;
+    my $this = shift->{client};
+    ASSERT($this->isa('TWiki::Client'));
+    my $sessionIsAuthenticated = $this->{sessionIsAuthenticated};
+
+    return '' if($sessionIsAuthenticated);
+
+    my $topic = $this->{twiki}->{topicName};
+    my $web = $this->{twiki}->{webName};
+    my $url = $this->{twiki}->getScriptUrl(
+        $web, $topic, "view", ( 'logout' => 1 ) );
+    return CGI::a( { href=> "@{ [$this->sessionLogonUrl()] }" },
+                   'Log In' );
+}
+
+sub _LOGOUT {
+    #($session, \%params, $web, $topic ) = @_;
+    my $this = shift->{client};
+    ASSERT($this->isa('TWiki::Client'));
+    my $sessionIsAuthenticated = $this->{sessionIsAuthenticated};
+
+    return "" if(!$sessionIsAuthenticated);
+
+    my $topic = $this->{twiki}->{topicName};
+    my $web = $this->{twiki}->{webName};
+    my $url = $this->{twiki}->getScriptUrl(
+        $web, $topic, 'view', ( 'logout' => 1 ) );
+    return CGI::a( { href => $url }, 'Log out' );
+}
+
+sub _AUTHENTICATED {
+    my( $session, $params ) = @_;
+    my $this = $session->{client};
+    ASSERT($this->isa('TWiki::Client'));
+
+    if( $this->{sessionIsAuthenticated} ) {
+        return $params->{then} || '';
+    } else {
+        return $params->{else} || '';
+    }
+}
+
+sub _SESSION_VARIABLE {
+    my( $session, $params ) = @_;
+    my $this = $session->{client};
+    ASSERT($this->isa('TWiki::Client'));
+    my $name = $params->{_DEFAULT};
+
+    if( defined( $params->{set} ) ) {
+        $this->setSessionValue( $name, $params->{set} );
+        return '';
+    } elsif( defined( $params->{clear} )) {
+        $this->clearSessionValue( $name );
+        return '';
+    } else {
+        return $this->getSessionValue( $name );
+    }
+}
+
+sub _SESSIONLOGONURL {
+    my( $session, $params ) = @_;
+    my $this = $session->{client};
+    ASSERT($this->isa('TWiki::Client'));
+    return $this->sessionLogonUrl();
+}
+
+sub _SESSIONLOGONURLPATH {
+    my( $session, $params ) = @_;
+    my $this = $session->{client};
+    ASSERT($this->isa('TWiki::Client'));
+    return $this->sessionLogonUrlPath();
+}
+
+sub _dispLogon {
+    my $this = shift;
+
+    my $topic = $this->{twiki}->{topicName};
+    my $web = $this->{twiki}->{webName};
+    my $sessionId = $this->{sessionId};
+    my $useTransSID = $this->{useTransSID};
+
+    my $urlToUse = $this->sessionLogonUrlPath();
+
+    $urlToUse .= ( '?' . $CGI::Session::NAME . '=' . $sessionId ) if $useTransSID;
+
+    my $logon = CGI::a({ class => 'twikiAlert', href => $urlToUse },
+                       'Log in' );;
+
+    return $logon;
+}
+
+sub _skinSelect {
+    my $this = shift;
+    my $stickSkinVar = $TWiki::cfg{StickSkinVar};
+    my $stickSkinOffValue = $TWiki::cfg{StickSkinOffValue};
+
+    my $skins = $this->{twiki}->{prefs}->getPreferencesValue("SKINS");
+    my $skin = $this->{twiki}->getSkin();
+    my @skins = split( /,/, $skins );
+    unshift @skins, $stickSkinOffValue;
+    my $options = '';
+    foreach my $askin ( @skins ) {
+        $askin =~ s/\s//go;
+        my $name = $askin;
+        $name = '.' if( $name eq $stickSkinOffValue );
+        if( $askin eq $skin ) {
+            $options .= CGI::option(
+                { selected => 'selected', name => $askin }, $askin );
+        } else {
+            $options .= CGI::option( { name=>$askin }, $askin );
+        }
+    }
+    return CGI::Select( { name=>$stickSkinVar }, $options );
+}
+
+1;
Index: lib/TWiki/Render.pm
===================================================================
--- lib/TWiki/Render.pm	(revision 4649)
+++ lib/TWiki/Render.pm	(working copy)
@@ -1189,6 +1189,7 @@
 
     # DEPRECATED plugins hook after PRE re-inserted
     $plugins->endRenderingHandler( $text );
+    $this->{session}->{client}->endRenderingHandler( $text );
 
     # replace verbatim with pre in the final output
     $this->putBackBlocks( \$text, $removed,
Index: lib/TWiki/Func.pm
===================================================================
--- lib/TWiki/Func.pm	(revision 4649)
+++ lib/TWiki/Func.pm	(working copy)
@@ -64,18 +64,18 @@
 
 ---+++ getSessionValue( $key ) -> $value
 
-Get a session value from the Session Plugin (if installed)
+Get a session value from the client session module
    * =$key= - Session key
-Return: =$value=  Value associated with key; empty string if not set; undef if session plugin is not installed
+Return: =$value=  Value associated with key; empty string if not set
 
-*Since:* TWiki::Plugins::VERSION 1.000 (27 Feb 200
+*Since:* TWiki::Plugins::VERSION 1.000 (27 Feb 200)
 
 =cut
 
 sub getSessionValue {
 #   my( $theKey ) = @_;
 
-    return $TWiki::Plugins::SESSION->{plugins}->getSessionValueHandler( @_ );
+    return $TWiki::Plugins::SESSION->{client}->getSessionValueHandler( @_ );
 }
 
 
Index: lib/TWiki/Client/TemplateLogin.pm
===================================================================
--- lib/TWiki/Client/TemplateLogin.pm	(revision 0)
+++ lib/TWiki/Client/TemplateLogin.pm	(revision 0)
@@ -0,0 +1,80 @@
+# TWiki Enterprise Collaboration Platform, http://TWiki.org/
+#
+# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com
+# and TWiki Contributors. All Rights Reserved. TWiki Contributors
+# are listed in the AUTHORS file in the root of this distribution.
+# NOTE: Please extend that file, not this notice.
+#
+# Additional copyrights apply to some or all of the code in this
+# file as follows:
+# Copyright (C) 2005 Greg Abbas, twiki@abbas.org
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version. For
+# more details read LICENSE in the root of this distribution.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# As per the GPL, removal of this notice is prohibited.
+
+=pod
+
+---+ package TWiki::Client::TemplateLogin
+Redirect to an template-based authentication page.
+
+=cut
+
+package TWiki::Client::TemplateLogin;
+
+use strict;
+use Assert;
+
+sub authenticate {
+    my( $pkg, $client ) = @_;
+
+    my $session = $client->{session};
+    my $query = $session->{cgiQuery};
+    my $topic = $session->{topicName};
+    my $web = $session->{webName};
+
+    unless( $session->{client}->{sessionIsAuthenticated} ) {
+        my $origurl = $query->url() . $query->path_info();
+        $origurl =~ s/([^0-9a-zA-Z-_.!*'()\/])/'%'.sprintf('%02x',ord($1))/ge;
+        my $url = $session->getScriptUrl( $web, $topic, 'login' ).
+          '?origurl='.$origurl;
+        $session->redirect( $url );
+        # this should use an exception. see the comment in Client.pm.
+        #exit 0;
+    }
+    return 0;
+}
+
+sub sessionLogonUrl {
+    my ( $pkg, $client ) = @_;
+    my $session = $client->{session};
+    my $topic = $session->{topicName};
+    my $web = $session->{webName};
+    my $sessionLogonUrl = $session->getScriptUrl( $web, $topic, "login" );
+}
+
+sub sessionLogonUrlPath {
+    my ( $pkg, $client ) = @_;
+    my $path = $pkg->sessionLogonUrl( $client );
+    $path =~ s@.*?//.*?/@/@;
+    return $path;
+}
+
+sub getUser {
+    # the current user is whoever the session says it is. we don't
+    # have any other information on that.
+}
+
+sub checkSession {
+    # it's all good.
+}
+
+1;
Index: lib/TWiki/Client/NoLogin.pm
===================================================================
--- lib/TWiki/Client/NoLogin.pm	(revision 0)
+++ lib/TWiki/Client/NoLogin.pm	(revision 0)
@@ -0,0 +1,57 @@
+# TWiki Enterprise Collaboration Platform, http://TWiki.org/
+#
+# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com
+# and TWiki Contributors. All Rights Reserved. TWiki Contributors
+# are listed in the AUTHORS file in the root of this distribution.
+# NOTE: Please extend that file, not this notice.
+#
+# Additional copyrights apply to some or all of the code in this
+# file as follows:
+# Copyright (C) 2005 Greg Abbas, twiki@abbas.org
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version. For
+# more details read LICENSE in the root of this distribution.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# As per the GPL, removal of this notice is prohibited.
+
+=pod
+
+---+ package TWiki::Client::NoLogin
+
+Don't ask for any usernames or passwords, ever.
+
+=cut
+
+package TWiki::Client::NoLogin;
+
+use strict;
+use Assert;
+
+sub authenticate {
+    return 0; # cowardly refuse to help.
+}
+
+sub sessionLogonUrl {
+    # there is no logon link...
+}
+
+sub sessionLogonUrlPath {
+    # ...there is only Zuul.
+}
+
+sub getUser {
+    # no user, no worries.
+}
+
+sub checkSession {
+    # it's all good.
+}
+
+1;
Index: lib/TWiki/Client/ApacheLogin.pm
===================================================================
--- lib/TWiki/Client/ApacheLogin.pm	(revision 0)
+++ lib/TWiki/Client/ApacheLogin.pm	(revision 0)
@@ -0,0 +1,124 @@
+# TWiki Enterprise Collaboration Platform, http://TWiki.org/
+#
+# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com
+# and TWiki Contributors. All Rights Reserved. TWiki Contributors
+# are listed in the AUTHORS file in the root of this distribution.
+# NOTE: Please extend that file, not this notice.
+#
+# Additional copyrights apply to some or all of the code in this
+# file as follows:
+# Copyright (C) 2005 Greg Abbas, twiki@abbas.org
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version. For
+# more details read LICENSE in the root of this distribution.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# As per the GPL, removal of this notice is prohibited.
+
+=pod
+
+---+ package TWiki::Client::ApacheLogin
+Redirect to a script to ask Apache to ask for a username & password.
+
+=cut
+
+package TWiki::Client::ApacheLogin;
+
+use strict;
+use Assert;
+
+sub authenticate {
+    my( $pkg, $client ) = @_;
+
+    my $session = $client->{session};
+    my $query = $session->{cgiQuery};
+
+    # Had an access control violation. See if there is an 'auth' version
+    # of this script, may be a result of not being logged in.
+    my $script = $ENV{'SCRIPT_FILENAME'};
+    $script =~ s/^(.*\/)([^\/]+)($TWiki::cfg{ScriptSuffix})?$/$1/;
+    my $scriptPath = $1;
+    my $scriptName = $2;
+    $script .= "$scriptPath${scriptName}auth$TWiki::cfg{ScriptSuffix}";
+    if( ! $query->remote_user() && -e $script ) {
+        my $url = $ENV{REQUEST_URI};
+        if( $url && $url =~ s/\/$scriptName/\/${scriptName}auth/ ) {
+            # $url i.e. is "twiki/bin/view.cgi/Web/Topic?cms1=val1&cmd2=val2"
+            $url = $session->{urlHost}.$url;
+        } else {
+            # If REQUEST_URI is rewritten and does not contain the script
+            # name, try looking at the CGI environment variable
+            # SCRIPT_NAME.
+            #
+            # Assemble the new URL using the host, the changed script name,
+            # the path info, and the query string.  All three query
+            # variables are in the list of the canonical request meta
+            # variables in CGI 1.1.
+            $scriptPath     = $ENV{'SCRIPT_NAME'};
+            my $pathInfo    = $ENV{'PATH_INFO'};
+            my $queryString = $ENV{'QUERY_STRING'};
+            $pathInfo    = '/' . $pathInfo    if ($pathInfo);
+            $queryString = '?' . $queryString if ($queryString);
+            if( $scriptPath && $scriptPath =~ s/\/$scriptName/\/${scriptName}auth/ ) {
+                $url = $session->{urlHost}.$scriptPath;
+            } else {
+                # If SCRIPT_NAME does not contain the script name
+                # the last hope is to try building up the URL using
+                # the SCRIPT_FILENAME.
+                $url = $session->{urlhost}.$session->{scriptUrlPath}.'/'.
+                    ${scriptName}.$TWiki::cfg{ScriptSuffix};
+            }
+            $url .= $pathInfo.$queryString;
+        }
+        $session->redirect( $url );
+        return 1;
+    }
+
+    return 0; # can't redirect to an auth script
+}
+
+sub sessionLogonUrl {
+    my ( $pkg, $client ) = @_;
+    my $session = $client->{session};
+    my $topic = $session->{topicName};
+    my $web = $session->{webName};
+    my $sessionLogonUrl = $session->getScriptUrl( $web, $topic, "logon" ); 
+}
+
+sub sessionLogonUrlPath {
+    my ( $pkg, $client ) = @_;
+    my $path = $pkg->sessionLogonUrl( $client );
+    $path =~ s@.*?//.*?/@/@;
+    return $path;
+}
+
+sub getUser {
+    my ( $pkg, $client ) = @_;
+    my $query = $client->{session}->{cgiQuery};
+    return $query->remote_user() if(defined($query));
+    return undef;
+}
+
+sub checkSession {
+    my ( $pkg, $client ) = @_;
+    my $cgisession = $client->{cgisession};
+    my $query = $client->{session}->{cgiQuery};
+    my $authUserSessionVar = $TWiki::Client::authUserSessionVar;
+
+    $cgisession->clear() if(
+        defined($cgisession) && defined($cgisession->param) && 
+          defined($query) && defined( $query->remote_user() ) &&
+            defined($authUserSessionVar) &&
+              defined( $cgisession->param( $authUserSessionVar ) ) &&
+                "" ne $query->remote_user() && 
+                  "" ne $cgisession->param( $authUserSessionVar ) &&
+                    $query->remote_user() ne $cgisession->param( $authUserSessionVar ) );
+}
+
+1;
Index: lib/TWiki/Users/HtPasswdUser.pm
===================================================================
--- lib/TWiki/Users/HtPasswdUser.pm	(revision 4649)
+++ lib/TWiki/Users/HtPasswdUser.pm	(working copy)
@@ -267,7 +267,8 @@
 
     $this->{error} = undef;
 
-    return 1 if( $encryptedPassword eq $this->fetchPass( $user ) );
+    my $pw = $this->fetchPass( $user );
+    return 1 if( defined($pw) && ($encryptedPassword eq $pw) );
 
     $this->{error} = 'Invalid user/password';
     return 0;
Index: lib/TWiki/Users.pm
===================================================================
--- lib/TWiki/Users.pm	(revision 4649)
+++ lib/TWiki/Users.pm	(working copy)
@@ -54,7 +54,7 @@
 
     $this->{session} = $session;
 
-	my $impl = $TWiki::cfg{PasswordManager};
+    my $impl = $TWiki::cfg{PasswordManager};
     eval "use $impl";
     die "Password Manager: $@" if $@;
     $this->{passwords} = $impl->new( $session );
@@ -246,7 +246,7 @@
     my $today = TWiki::Time::formatTime(time(), '$day $mon $year', 'gmtime');
 
     # add to the cache
-    $this->{U2W}{$user->login()} = $user->{web} . "." , $user->wikiName();
+    $this->{U2W}{$user->login()} = $user->{web} . "." . $user->wikiName();
 
     # add name alphabetically to list
     foreach my $line ( split( /\r?\n/, $text) ) {
Index: lib/TWiki/UI/Register.pm
===================================================================
--- lib/TWiki/UI/Register.pm	(revision 4649)
+++ lib/TWiki/UI/Register.pm	(working copy)
@@ -655,7 +655,7 @@
     my $topic = $session->{topicName};
     my $web = $session->{webName};
 
-    #    $this->{session}->writeLog('verifyuser', $loginName, "$userName");
+    #    $session->writeLog('verifyuser', $loginName, "$userName");
 
     _emailRegistrationConfirmations( $session, $data );
 }
@@ -710,6 +710,11 @@
                                               $data->{WikiName},
                                               $data->{LoginName} );
 
+    # let the client session know that we're logged in. (This probably
+    # eliminates the need for the registrationHandler call above,
+    # but we'll leave them both in here for now.)
+    $session->{client}->userLoggedIn( $data->{LoginName}, $data->{WikiName} );
+    
     # add user to TWikiUsers topic
     my $user = $session->{users}->findUser( $data->{LoginName},
                                             $data->{WikiName} );
Index: lib/TWiki/UI/Edit.pm
===================================================================
--- lib/TWiki/UI/Edit.pm	(revision 4649)
+++ lib/TWiki/UI/Edit.pm	(working copy)
@@ -98,6 +98,10 @@
     my $extra = '';
     my $topicExists  = $store->topicExists( $webName, $topic );
 
+    # If you want to edit, you have to be able to view and change.
+    TWiki::UI::checkAccess( $session, $webName, $topic, 'view', $user );
+    TWiki::UI::checkAccess( $session, $webName, $topic, 'change', $user );
+
     # Check lease, unless we have been instructed to ignore it
     my $breakLock = $query->param( 'breaklock' ) || '';
     unless( $breakLock ) {
@@ -155,12 +159,6 @@
           $store->readTopic( undef, $webName, $topic, undef );
     }
 
-    # If you want to edit, you have to be able to view and change.
-    TWiki::UI::checkAccess( $session, $webName, $topic,
-                            'view', $session->{user} );
-    TWiki::UI::checkAccess( $session, $webName, $topic,
-                            'change', $session->{user} );
-
     if( $saveCmd && ! $session->{user}->isAdmin()) {
         throw TWiki::OopsException( 'accessdenied', def=>'only_group',
                                     web => $webName, topic => $topic,
Index: lib/TWiki/UI/View.pm
===================================================================
--- lib/TWiki/UI/View.pm	(revision 4649)
+++ lib/TWiki/UI/View.pm	(working copy)
@@ -92,6 +92,8 @@
     if( $topicExists ) {
         ( $currMeta, $currText ) = $store->readTopic
           ( $session->{user}, $webName, $topicName, undef );
+        TWiki::UI::checkAccess( $session, $webName, $topicName,
+                                'view', $session->{user}, $currText );
         ( $revdate, $revuser, $showRev ) = $currMeta->getRevisionInfo();
         $revdate = TWiki::Time::formatTime( $revdate );
 
Index: bin/configure
===================================================================
--- bin/configure	(revision 4649)
+++ bin/configure	(working copy)
@@ -118,6 +118,8 @@
    'POSIX'            => "I18N (core module) and Security",
    'Digest::MD5'      => "MD5 encoded passwords",
    'Text::Diff'       => 'UpgradeTWiki',
+   'CGI::Cookie'      => "sessions",
+   'CGI::Session'     => "sessions",
   };
 
 my $I18Mods =
@@ -869,6 +871,38 @@
     return $mess;
 };
 
+sub _CHECKVAR_UseClientSessions {
+    if( $TWiki::cfg{UseClientSessions} ) {
+        if (!eval "use CGI::Cookie; 1") {
+            return ERROR("
+ The CGI::Cookie Perl module is required for session support, but is not
+ available. Please either install it or turn UseClientSessions off.
+                     ");
+        }
+        if (!eval "use CGI::Session; 1") {
+            return ERROR("
+ The CGI::Session Perl module is required for session support, but is not
+ available. Please either install it or turn UseClientSessions off.
+                     ");
+        }
+    }
+    return '';
+}
+
+sub _CHECKVAR_AuthScripts {
+    if( $TWiki::cfg{AuthScripts} ) {
+        if( $TWiki::cfg{LoginManager} eq "TWiki::Client::NoLogin" ) {
+            return WARN("
+You've asked that some scripts require authentication, but haven't
+specified a way for users to log in. Please pick a LoginManager
+(above) other than NoLogin.
+                     ");
+        }
+    }
+    return '';
+}
+
+
 # Generates the appropriate HTML for getting a value to configure the
 # entry. The opts are additional parameters, and by convention may
 # be a number (for a string length), a comma separated list of values
Index: data/TWiki/TWikiPreferences.txt
===================================================================
--- data/TWiki/TWikiPreferences.txt	(revision 4649)
+++ data/TWiki/TWikiPreferences.txt	(working copy)
@@ -80,6 +80,9 @@
 		  <img src="%ATTACHURLPATH%/$name" alt="$comment" $size />
 </verbatim>
 
+---++ Session settings
+   * Standard login message
+      * Set LOGON_OR_WELCOME = %SESSION_IF_AUTHENTICATED% *[[TWiki.WelcomeGuest][Welcome]]* %WIKIUSERNAME% %SESSION_ELSE% [[TWiki.TWikiRegistration][Register]] or %SESSIONLOGON% %SESSION_ENDIF%
 
 ---++ TWiki Platform Settings
 
