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=)) { 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=)) { ($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() { 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=)) { $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() { 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;