#!/usr/bin/perl -w
#
##
#
# UNIXPASSWD - Openradius module that verifies passwords using the Unix
# password database. If your host system supports shadow password using
# the standard getpw*() interface, this module also supports them. 
# The same goes for Crypt(3)-style MD5-hashed passwords, NIS and NIS+.
#
# If shadow password support is available and you want to use it, you'll
# need to make this module setuid root after installation.
#
##
#
# Usage: unixpasswd [-d]
#	 unixpasswd -h
# 
# -d increases verbosity on stderr and allows module to run standalone
#
# The module uses the last 'str' attribute from incoming requests as 
# the username, and the last 'User-Password' attribute as the password. 
# Returns one instance of 'int', with value 1 if the credentials matched 
# and 0 otherwise.
#
# FIXME: cache usernames / passwords in a hash and refresh when
# signalled or after a configurable period. Possibly optional using -c
# switch; some people may have fast getpwname() implementations or want
# immediately updated passwords anyway. I'd like to see some reports
# that it *is* indeed slow first, though.
#
# All in all, it's still quite primitive right now. Any improvements are
# warmly welcomed, especially perhaps native shadow support. But that 
# would _definitely_ be better done after doing the hashing cache.
#
##
#
# Author:
# Emile van Bergen, emile@evbergen.xs4all.nl
#
# Permission to redistribute an original or modified version of this program
# in source, intermediate or object code form is hereby granted exclusively
# under the terms of the GNU General Public License, version 2. Please see the
# file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
#
# History:
# 2001/11/21 - EvB - Created
#
##


########
# USES #
########

use Getopt::Long;


###########
# GLOBALS #
###########

$debug = 0;


########
# MAIN #
########

# Get options
Getopt::Long::Configure("bundling");
GetOptions("h"  => \$usage,
	   "d+" => \$debug);
if ($usage) { die("Usage: unixpasswd [-d]\n       unixpasswd -h\n"); }

# Check that we're running under OpenRADIUS, interface version 1
unless ($debug ||
	$ENV{'RADIUSINTERFACEVERSION'} &&
	$ENV{'RADIUSINTERFACEVERSION'} == 1) {
	die("unixpasswd: ERROR: not running under OpenRADIUS, interface v1!\n");
}

# Set record separator to empty line and loop on input.
$/ = "\n\n";
$| = 1;			# Important - we're outputting to a pipe
my $user; my $pass;
while(<STDIN>) {

	# Parse pairs from server's request message
	chomp;
	$user = $pass = "";
	/^\s*str\s*=\s*"(.*)"\s*$/m and $user = $1;
	/^\s*User-Password\s*=\s*"(.*)"\s*$/m and $pass = $1;

	# Debugging
	$debug and print STDERR "unixpasswd[$$]: got request: $_\n" .
				"unixpasswd[$$]: user: $user, pass: $pass\n";

	# First find the user in the database
	(undef, my $unixpass, my $uid, my $gid) = getpwnam($user);
	unless ($unixpass) {
		$debug and print STDERR "unixpasswd[$$]: not found!\n";
		print "int=0\n\n";
		next;
	}
	$debug and print STDERR "unixpasswd[$$]: found, uid: $uid, gid: $gid, ".
				"pass: $unixpass\n";

	# Now verify the password
	if (crypt($pass, $unixpass) eq $unixpass) {
		$debug and print STDERR "unixpasswd[$$]: password matches\n";
		print "int=1\n\n";
		next;
	}

	$debug and print STDERR "unixpasswd[$$]: wrong password!\n";
	print "int=0\n\n";
}

