package openwebmail::auth_unix_ensim;
use strict;
# 
# auth_unix_ensim.pl -  authenticate user with unix password and check
#                       if user is valid under the host
#
# Version 1.16 Jun 26, 2003 UNOFFICIAL RELEASE
# Update by mitch@bluecrow.net
# Added support for versions of OpenWebMail greater than 1.90
# THIS VERSION WILL NOT WORK WITH OPENWEBMAIL VERSIONS LESS THAN 2!!!
# I have only tested it under OPENWEBMAIL version 2.10.
#
# April 2004: Tested using Openwebmail 2.30.  DOES NOT WORK YET!!!
#
# In OWM version 2 (greater than 1.90), an additional parameter was passed
# to the functions in ALL of the authentication modules.  This parameter
# is designated as $r_config in the documentation.  It is a HASH
# containing the configuration of OWM.  The fix was to change the $user
# and other assignment statements from =@_[0] to =@_[1], or whatever the
# number just increment by 1.
#
# The other problem that I ran into was there were numberous calls to
# a function filelock in auth_unix_ensim.pl and the compiler complained
# that that function could not be found.  The calls to filelock were
# changed to calls to flock.
#
# I tested it out as far as logging in is concerened, but have not tested
# the change password function.  I have disabled it as I don't really want
# to use it.
#
# This is my first whack at Perl, so I'm sure the fix could be better.
#
# TODO: update the script so that it can be used with any version of
# OpenWebMail.  To do this, the script needs to detect if @_[0] is a
# HASH.  If it is not, it is a version <2.  If it is, the version is
# greater than 1.90.
#
# Version 1.15 Aug 6, 2002
#
# 2002/08/06 Trevor.Paquette@TeraGo.ca
#            Fix comments 
# 2001/12/20 tung@turtle.ee.ncku.edu.tw (orig: auth_unix.pl)
#
# This auth module will check if the authenticated user has a 
# directory in /home/virtual/DOMAIN/home/USER, and if exists the 
# shell informed in domains´s passwd.
#
# Use this module in conjunction with allowed_serverdomain to lock
# down which domains actually have access to webmail.
#
# $unix_passwdfile_plaintext : the plaintext file containing all usernames
#                              and related uid, gid, homedir, shell info.
#                              The deault is /etc/passwd on most unix systems.
# $unix_passwdfile_encrypted : the file containing all usernames and
#                              their corresponding encrypted passwords. 
# $unix_passwdmkdb : The command executed after any password modification 
#                    to update the changes of passwdfile to passwd database.

my $unix_passwdfile_plaintext="/etc/passwd";
my $unix_passwdfile_encrypted="/etc/shadow";
my $unix_passwdmkdb="none";

################### No configuration required from here ###################

use strict;
use Fcntl qw(:DEFAULT :flock);

sub get_userinfo {
   my $user=$_[1];
   my ($uid, $gid, $realname, $homedir);
   my $domain;

   ($user, $domain)=split(/@/, $user);

   $unix_passwdfile_plaintext = "/home/virtual/$domain$unix_passwdfile_plaintext" if ($domain ne "" && $unix_passwdfile_plaintext !~ /^\/home\/virtual/);

   if ($unix_passwdfile_plaintext eq "/etc/passwd") {
      ($uid, $gid, $realname, $homedir)= (getpwnam($user))[2,3,6,7];
   } else {
      ($uid, $gid, $realname, $homedir)= (getpwnam_file($user, $unix_passwdfile_plaintext))[2,3,6,7];
   }

   # use first field only
   $realname=(split(/,/, $realname))[0];
   # guess real homedir under sun's automounter
   if ($uid) {
      $homedir="/export$homedir" if (-d "/export$homedir");
   }
   $homedir = "/home/virtual/$domain$homedir" if ($domain ne "");
   return(0, "", $realname, $uid, $gid, $homedir);
}


sub get_userlist {      # only used by checkmail.pl -a
   my @userlist=();
   my $line;   

   # a file should be locked only if it is local accessable
   flock("$unix_passwdfile_encrypted", LOCK_SH) if ( -f $unix_passwdfile_encrypted);
   open(PASSWD, $unix_passwdfile_encrypted);
   while (defined($line=<PASSWD>)) {
      push(@userlist, (split(/:/, $line))[0]);
   }
   close(PASSWD);
   flock("$unix_passwdfile_encrypted", LOCK_UN) if ( -f $unix_passwdfile_encrypted);

   return(@userlist);
}


#  0 : ok
# -2 : parameter format error
# -3 : authentication system/internal error
# -4 : password incorrect
sub check_userpassword {
   my ($user)=@_[1];
   my ($password)=@_[2];
   my ($line, $u, $p);
   my $domain;

   ($user, $domain)=split(/@/, $user);

   return -2 unless ( $user ne "" && $password ne "");

   $unix_passwdfile_encrypted = "/home/virtual/$domain$unix_passwdfile_encrypted" if ($domain ne "" && $unix_passwdfile_encrypted !~ /^\/home\/virtual/);
   $unix_passwdfile_plaintext = "/home/virtual/$domain$unix_passwdfile_plaintext" if ($domain ne "" && $unix_passwdfile_plaintext !~ /^\/home\/virtual/);

   # a file should be locked only if it is local accessable
   flock("$unix_passwdfile_encrypted", LOCK_SH) if ( -f $unix_passwdfile_encrypted);
   if ( ! open (PASSWD, "$unix_passwdfile_encrypted") ) {
      flock("$unix_passwdfile_encrypted", LOCK_UN) if ( -f $unix_passwdfile_encrypted);
      return -3;
   }
   while (defined($line=<PASSWD>)) {
      ($u, $p) = (split(/:/, $line))[0,1];
      last if ($u eq $user); # We've found the user in /etc/passwd
   }
   close (PASSWD);
   flock("$unix_passwdfile_encrypted", LOCK_UN) if ( -f $unix_passwdfile_encrypted);

   return -4 if ($u ne $user || crypt($password,$p) ne $p);

   # validate user shell if /etc/shells exists
   if (open(ES, "/etc/shells")) {
      my $shell;
      if ($unix_passwdfile_plaintext eq "/etc/passwd") {
         $shell = (getpwnam($user))[8];
      } else {
         $shell = (getpwnam_file($user, $unix_passwdfile_plaintext))[8];
      }
      my $validshell = 0;   # assume an invalid shell until we get a match
      while(<ES>) {
         chop;
         if( $shell eq $_ ) {
            $validshell = 1; last;
         }
      }
      close(ES);
      return -4 if (!$validshell);
   }
         
   return 0;
}


#  0 : ok
# -1 : function not supported
# -2 : parameter format error
# -3 : authentication system/internal error
# -4 : password incorrect
sub change_userpassword {
   my ($user)=@_[1];
   my ($oldpassword)=@_[2];
   my ($newpassword)=@_[3];
   my ($u, $p, $misc, $encrypted);
   my $content="";
   my $line;
   my $domain;

   return -2 unless ( $user ne "" && $oldpassword ne "" && $newpassword ne "" );
   return -2 if (length($newpassword)<4);

   ($user, $domain)=split(/@/, $user);
   $unix_passwdfile_encrypted = "/home/virtual/$domain$unix_passwdfile_encrypted" if ($domain ne "" && $unix_passwdfile_encrypted !~ /^\/home\/virtual/);

   # a passwdfile could be modified only if it is local accessable
   return -1 if (! -f $unix_passwdfile_encrypted);

   flock("$unix_passwdfile_encrypted", LOCK_EX);
   open (PASSWD, $unix_passwdfile_encrypted) or return -3;
   while (defined($line=<PASSWD>)) {
      $content .= $line;
      if ($u ne $user) {
         ($u, $p, $misc) = split(/:/, $line, 3);
      }
   }
   close (PASSWD);

   if ($u ne $user || crypt($oldpassword,$p) ne $p) {
      flock("$unix_passwdfile_encrypted", LOCK_UN);
      return -4;
   }

   srand();
   my $table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
   my $salt=substr($table, int(rand(length($table))), 1).
            substr($table, int(rand(length($table))), 1);

   if ($p =~ /^\$1\$/) {        # if orig encryption is MD5, keep using it
      $salt = '$1$'. $salt;
   }

   $encrypted= crypt($newpassword, $salt);

   my $oldline=join(":", $u, $p, $misc);
   my $newline=join(":", $u, $encrypted, $misc);

   if ($content !~ s/\Q$oldline\E/$newline/) {
      flock("$unix_passwdfile_encrypted", LOCK_UN);
      return -3;
   }

   open(TMP, ">$unix_passwdfile_encrypted.tmp.$$") || goto authsys_error;
   print TMP $content || goto authsys_error;
   close(TMP) || goto authsys_error;

   if ($unix_passwdmkdb ne "" && $unix_passwdmkdb ne "none" ) {
      # update passwd and db with pwdmkdb program
      if ( system("$unix_passwdmkdb $unix_passwdfile_encrypted.tmp.$$")!=0 ) {
         goto authsys_error;
      }
   } else {
      # automic update passwd by rename
      my ($fmode, $fuid, $fgid) = (stat($unix_passwdfile_encrypted))[2,4,5];
      chown($fuid, $fgid, "$unix_passwdfile_encrypted.tmp.$$");
      chmod($fmode, "$unix_passwdfile_encrypted.tmp.$$");
      rename("$unix_passwdfile_encrypted.tmp.$$", $unix_passwdfile_encrypted) || goto authsys_error;
   }
   flock("$unix_passwdfile_encrypted", LOCK_UN);
   return(0);

authsys_error:
   unlink("$unix_passwdfile_encrypted.tmp.$$");
   flock("$unix_passwdfile_encrypted", LOCK_UN);
   return(-3);
}

# this routie is slower than system getpwnam() but can work with file 
# other than /etc/passwd. ps: it always return '*' for passwd field.
sub getpwnam_file {
   my ($user, $passwdfile_plaintext)=@_;
   my ($name, $passwd, $uid, $gid, $gcos, $dir, $shell);

   return("", "", "", "", "", "", "", "", "") if ($user eq "" || ! -f $passwdfile_plaintext);

   open(PASSWD, "$passwdfile_plaintext");
   while(<PASSWD>) {
      next if (/^#/);
      chomp;
      ($name, $passwd, $uid, $gid, $gcos, $dir, $shell)=split(/:/);
      last if ($name eq $user);
   }
   close(PASSWD);

   if ($name eq $user) {
      return($name, "*", $uid, $gid, 0, "", $gcos, $dir, $shell);
   } else {
      return("", "", "", "", "", "", "", "", "");
   }
}

1;
