#!/usr/bin/perl #============================================================================= # Cleaner for LemonLDAP::NG: removes old sessions from Apache::Session # # This module is written to be used by cron to clean old sessions from # Apache::Session. It does not works with Apache::Session::Memcached # # This is part of LemonLDAP::NG product, released under GPL #============================================================================= use Lemonldap::NG::Common::Conf; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::Apache::Session; use Lemonldap::NG::Common::Session; use strict; use Getopt::Std; # Options # -d: debug mode # -f: force delete of corrupted sessions our $opt_d; our $opt_f; getopts('df'); my $debug = $opt_d; my $force = $opt_f; my $nb_purged = 0; my $nb_error = 0; #============================================================================= # Load configuration #============================================================================= my $lmconf = Lemonldap::NG::Common::Conf->new() or die $Lemonldap::NG::Common::Conf::msg; my $conf = $lmconf->getConf or die "Unable to get configuration ($!)"; my $localconf = $lmconf->getLocalConf(PORTALSECTION) or die "Unable to get local configuration ($!)"; if ($localconf) { $conf->{$_} = $localconf->{$_} foreach ( keys %$localconf ); } print "Configuration loaded\n" if $debug; #============================================================================= # Timeout #============================================================================= print "Timeout value: " . $conf->{timeout} . "\n" if $debug; #============================================================================= # Apache::Session backends #============================================================================= my @backends; my $module; # Sessions if ( defined $conf->{globalStorage} ) { # Load module $module = $conf->{globalStorage}; eval "use $module"; die $@ if ($@); $conf->{globalStorageOptions}->{backend} = $module; # Add module in managed backends push @backends, $conf->{globalStorageOptions}; print "Session backend $module will be used\n" if $debug; } # SAML if ( defined $conf->{samlStorage} or keys %{ $conf->{samlStorageOptions} } ) { # Load module $module = $conf->{samlStorage} || $conf->{globalStorage}; eval "use $module"; die $@ if ($@); $conf->{samlStorageOptions}->{backend} = $module; # Add module in managed backends push @backends, $conf->{samlStorageOptions}; print "SAML backend $module will be used\n" if $debug; } # CAS if ( defined $conf->{casStorage} or keys %{ $conf->{casStorageOptions} } ) { # Load module $module = $conf->{casStorage} || $conf->{globalStorage}; eval "use $module"; die $@ if ($@); $conf->{casStorageOptions}->{backend} = $module; # Add module in managed backends push @backends, $conf->{casStorageOptions}; print "CAS backend $module will be used\n" if $debug; } # Captcha if ( defined $conf->{captchaStorage} or keys %{ $conf->{captchaStorageOptions} } ) { # Load module $module = $conf->{captchaStorage} || $conf->{globalStorage}; eval "use $module"; die $@ if ($@); $conf->{captchaStorageOptions}->{backend} = $module; # Add module in managed backends push @backends, $conf->{captchaStorageOptions}; print "Captcha backend $module will be used\n" if $debug; } # OpenIDConnect if ( defined $conf->{oidcStorage} or keys %{ $conf->{oidcStorageOptions} } ) { # Load module $module = $conf->{oidcStorage} || $conf->{globalStorage}; eval "use $module"; die $@ if ($@); $conf->{oidcStorageOptions}->{backend} = $module; # Add module in managed backends push @backends, $conf->{oidcStorageOptions}; print "OIDC backend $module will be used\n" if $debug; } #============================================================================= # Load and purge sessions #============================================================================= for my $options (@backends) { next if ( $options->{backend} eq "Apache::Session::Memcached" ); my @t; if ( $options->{backend}->can('deleteIfLowerThan') ) { next if $options->{backend}->deleteIfLowerThan( $options, { not => { '_session_kind' => 'Persistent' }, or => { _utime => time - $conf->{timeout}, ( $conf->{timeoutActivity} ? ( _lastSeen => time - $conf->{timeoutActivity} ) : () ) } } ); } # Get all expired sessions Lemonldap::NG::Common::Apache::Session->get_key_from_all_sessions( $options, sub { my $entry = shift; my $id = shift; my $time = time; print "Check session $id\n" if $debug; # Empty session need to be removed unless ($entry) { push @t, $id; print "Session $id is empty (corrupted?), delete forced\n" if $debug; } # Do net check sessions without _utime return undef unless $entry->{_utime}; # Do not expire persistent sessions return undef if ( $entry->{_session_kind} eq "Persistent" ); # Session expired if ( $time - $entry->{_utime} > $conf->{timeout} ) { push @t, $id; print "Session $id expired\n" if $debug; } # User has no activity, so considere the session has expired elsif ( $conf->{timeoutActivity} and $entry->{_lastSeen} and $time - $entry->{_lastSeen} > $conf->{timeoutActivity} ) { push @t, $id; print "Session $id inactive\n" if $debug; } undef; } ); # Delete sessions my @errors; for my $id (@t) { my $session = Lemonldap::NG::Common::Session->new( storageModule => $options->{backend}, storageModuleOptions => $options, cacheModule => $conf->{localSessionStorage}, cacheModuleOptions => $conf->{localSessionStorageOptions}, id => $id, ); unless ( $session->data ) { print "Error while opening session $id\n" if $debug; print STDERR "Error on session $id\n"; $nb_error++; push @errors, $id; next; } unless ( $session->remove ) { print "Error while deleting session $id\n" if $debug; print STDERR "Error on session $id\n"; $nb_error++; push @errors, $id; next; } print "Session $id has been purged\n" if $debug; $nb_purged++; } # Remove lock files for File backend if ( $options->{backend} =~ /^Apache::Session::(?:Browseable::)?File$/i ) { require Apache::Session::Lock::File; my $l = new Apache::Session::Lock::File; my $lock_directory = $options->{LockDirectory} || $options->{Directory}; $l->clean( $lock_directory, $conf->{timeout} ); } # Force deletion of corrupted sessions for File backend if ( $options->{backend} =~ /^Apache::Session::(?:Browseable::)?File$/i and $force ) { foreach (@errors) { my $id = $_; eval { unlink $options->{Directory} . "/$id"; }; if ($@) { print STDERR "Unable to remove session $id\n"; } else { print STDERR "Session $id removed with force\n"; $nb_error--; } } } } #============================================================================= # Exit #============================================================================= print "$nb_purged sessions have been purged\n" if $debug; print STDERR "$nb_error sessions remaining, try to purge them with force (option -f)\n" if $nb_error; my $exit = $nb_error ? 1 : 0; exit $exit;