# Blosxom Plugin: Login2
# Author: login by Fletcher T. Penney, modified to login2 by Daniel C. Parker
# Version: login 0.4 modified to login2 0.1
# NOTE: a functional cookies plugin is required.  Apparently, it has to be named something like
# 9999cookies to work properly.
#
# Also, this plugin works by modifying the filter subroutine.  You may need to rename your plugins to insure
# the proper order, but it works properly with exclude, and menu to my testing.  I am sure there are possible
# conflicts with other plugins though.
package login2;
# --- Configurable variables -----
# Where is the passwd file?  Should not be in a public directory nor group/world writeable
# This file is created with the htpasswd command (Do a man htpasswd to figure it out - please don't ask
# me to explain how to do this - there are plenty of references on the web.
# I will work on updating this to allow a cgi program to maintain this file, but can't guarantee
# when that will occur.  In the meantime, this provides for better security.
my $passwd_file = "$blosxom::plugin_state_dir/.htpasswd";
# Where is your excludefile - this controls which user is able to see which files
# the excludefile is built using regular expressions in the format:
# user=page
# user is a regexp to match allowed user (.* matches all VALIDATED users)
# page is a regexp to match pages or directories (.* matches all pages)
#
# Examples:
# A blank or nonexistent file allows full access to anyone, logged in or not
# myusername=.*
#  Only someone logged in as myusername can access any files on the web site
# (myuser1|myuser2)=private
#  Only these two people can access a file or directory named private, private1, privatestuff, etc
# .*=priv$
#  Any VALIDATED user can view the directory named priv, but this doesn't affect one named private
#
# 
my $excludefile = "$blosxom::plugin_state_dir/requireuser";
#Minutes of inactivity for automatic logout.
my $min = 5;
my $default_message = "Please log in.";
# This section for gui creation of accounts
my $pending_file = "$blosxom::plugin_state_dir/pendingaccounts";
# --------------------------------
# Ideas for improvement
# Option to log all succesful logins
# Ability to set a sort of exclude file to filter out entries by login name
use CGI qw/:standard/;
$username = "";    # users login name, if any - set only after password is validated
$path_noflavour = "";
$message = "";
my $passphrase ="";
my $validated = 0;
sub login_now {
  my ($user, $pass) = @_;
  my $time = time();
  &cookies::add(
    cookie(
      -name=>'login',
      -value=>{ 'user' => $user, 'pass' => $pass, 'time' => $time},
      -domain=>$cookies::domain,
      -expires=>$cookies::expires
    )
  );
  #Record login time to password file
  if(open(PASSWD, $passwd_file)) {
    #read file
    my @PASS = <PASSWD>;
    close(PASSWD);
    #change time
    for $i (@PASS) {
      my @line = split(/:/, $PASS[$i]);
      if($line[0] eq $user) {
        $PASS[$i] = "$line[0]:$line[1]:$time\n";
      }
    }
    #write file
    if(open(PSS, ">$passwd_file")) {
      print PSS @PASS;
      close(PSS);
    }
  }
}
sub validate {
  my ($submituser, $submitpass, $submittime) = @_;
  if (open(PASSWD, $passwd_file)) {
    while (<PASSWD>) {
      chomp;
      ($testuser, $testpass, $testtime) = split(/:/,$_);
      if ($testuser eq $submituser) {
        if (crypt($submitpass,$testpass) eq $testpass) {
          my $inactive = $submittime-$testtime;
          if($submittime-$testtime < $min*60) {
            $message = "Welcome, $submituser.\n";
            $username = $submituser;
            $validated = 1;
            $loginform = $logoutform;
          }
        } else {
          warn "blosxom : login plugin > Incorrect password for user $submituser.";
        }
      }
    }
    close(PASSWD);
    if($validated) {&login_now($submituser, $submitpass)}
    
  } else {
    $message =  "Unable to access password file.\n";
    warn "blosxom : login plugin > $message";
    $username = "";
  }
  return $validated;
}
sub start {
  open (REQUIRE, $excludefile);
  @requiredlist = <REQUIRE>;
  close REQUIRE;
# HTML code for the login form to be displayed
# Converts to a logout button as well?
$loginform = qq!<form method="post" action="."> Login: <input name="user" size="10" value="" /><br /> Password: <input name="pass" size="10" value="" type="password" /><br />
<input type="submit" value="Login" />
<input type="hidden" name="plugin" value="login" />
<input type="hidden" name="task" value="login" />
</form>!;
$logoutform = qq!<form method="POST" action=".">
<input type="submit" value="Logout" />
      <input type="hidden" name="plugin" value="login" />
      <input type="hidden" name="task" value="logout" />
</form>!;
$signupform = qq!<form method="POST" action=".">
<table border=0 cellpadding=0 cellspacing=0>
<tr>
<td align=right>Login(no spaces):</td><td><input name="user" size="10" value="" ><br></td>
</tr><tr>
<td align=right>Email:</td><td><input name="name" size="10" value=""><br></td>
</tr><tr>
<td align=right>Password:</td><td><input name="pass" size="10" value="" type="password"><br></td>
</tr><tr>
<td align=right>Verify:</td><td><input name="verifypass" size="10" value="" type="password"><br></td>
</tr><tr>
<td colspan=2 align=center><input type="submit" value="Add User"></td>
</tr></table>
<input type="hidden" name="plugin" value="login">
<input type="hidden" name="task" value="signup">
</form>!;
  1;
}
sub filter {
  my ($pkg, $files_ref) = @_;
  my @files_list = keys %$files_ref;
  # This handles login requests and creates a cookie, if valid
  if ( request_method() eq 'POST' and (param('plugin') eq 'login')) {
    if (param('task') eq 'logout') {
      &cookies::remove('login');  # These don't seem to work
      &cookies::clear('login');  # So I overwrite the cookie below
        &cookies::add(
          cookie(
            -name=>'login',
            -value=>'',
            -domain=>$cookies::domain,
            -expires=>'now'
          )
        );
      $username="";
      $validated=-1;
    }
    if (param('task') eq 'login') {
      my $user = param('user');
      my $pass = param('pass');
      my $time = time();
      if (validate($user,$pass,0)) {
        # Create a cookie
        &login_now($user,$pass);
      } else {
        $message = "Login for user $user failed.";
        warn "blosxom : login plugin > $message";
        $username = "";
      }
    }
    if (param('task') eq 'signup') {
      my $user = param('user');
      my $pass = param('pass');
      my $verify = param('verifypass');
      my $name = param('name');
      $validated=-1;
      $message= "";
      if ($pass ne $verify) {
        $message = "Your passwords were different.  Please try again.";
      } 
      if (length($pass) < 5) {
        $message = "Come on.... Your password has to be at least 5 characters.";
      }
      if ($message eq "") {
        open(PENDING,">>$pending_file") || ($message = "Error submitting information.  Please try again.");
        if ($message eq "") {
          my @salts = (a..z,A..Z,0..9,'.','/'); 
          my $salt=$salts[rand @salts];
            $salt.=$salts[rand @salts];
            my $encrypted=crypt($pass,$salt);
          print PENDING "$name $email requests $user:$encrypted\n";
          close PENDING;
          $message="Request submitted. We'll add you to the user list in a short time.";
        }
      }
    }
  }
  # Now, read cookies to see if user is logged in
  if (my $cookie = &cookies::get('login') and $validated eq 0) {
    my $user = $cookie->{'user'};
    my $pass = $cookie->{'pass'};
    my $time = $cookie->{'time'};
    if($user and $pass) {
      validate($user,$pass,time());
    }
  }
  $message = $default_message if ($message eq "");
1;
}
1;
__END__
=head1 NAME
Blosxom Plug-in: login
=head1 DESCRIPTION
Login allows you to create a passwd file that defines usernames and passwords for your web site.  Another file, $excludefile, defines which users can access certain parts of your site.  The format for the files is explained above.
$login::username provides the users login, once it has been validated against the database.  It remains "" if someone is not logged in.
$login::loginform provides the login/logout box.  Place it in your templates wherever you like.
$login::message gives certain status messages.
By default, login provides logging messages when certain errors occur, and when a failed login attempt happens.
$login::signupform provides html code for a simple account sign up form.  Once a user submits the information, it is added to a text file defined by $pending_file.  If you want to add the account, simply copy the user and password hash to the defined password file in the format "user:hash" as defined by the htpasswd command.
PLEASE NOTE:  I am not a security expert.  I can't guarantee that there isn't a huge loophole in this somewhere.  And note - any malicious plug-in could expose a whole by tampering with the $username variable - verify that other plugins do not attempt to manipulate $login::username or access the cookies.  But this is more a matter of being careful which plugins you install.
Also, this plugin does not pretend to offer serious security.  Someone could read a restricted file if they have read access to the location that the file is stored.  This could occur if you datadir is in your web servers documentroot.  
There are many issues to consider when securing a web site, and I don't pretend to address them all, nor do I guarantee that this plugin even works properly.  It is simply a means to provide some form of control without much effort on your part.  You get what you pay for.  But I will continue to work with the community to improve it as security holes are noted.
=head1 AUTHOR
Fletcher T. Penney - http://fletcher.freeshell.org
Daniel C. Parker, <mailto:writedanielp@hotmail.com> - http://www.theserviceguys.com/dan 
=head1 LICENSE
This source is submitted to the public domain.  Feel free to use and modify it.  If you like, a comment in your modified source attributing credit for my original work would be appreciated.
THIS SOFTWARE IS PROVIDED AS IS AND WITHOUT ANY WARRANTY OF ANY KIND.  USE AT YOUR OWN RISK!