Skip to content
Snippets Groups Projects
smradiusd 29.1 KiB
Newer Older
Nigel Kukard's avatar
Nigel Kukard committed
#!/usr/bin/perl
# Radius daemon
# Copyright (C) 2008, AllWorldIT
# Copyright (C) 2007, Nigel Kukard  <nkukard@lbsd.net>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


use strict;
use warnings;

# Set library directory
use lib qw(
	../ ./ 
	smradius/modules/authentication
	smradius/modules/userdb
	smradius/modules/accounting
	smradius/modules/features
Nigel Kukard's avatar
Nigel Kukard committed
);

package radiusd;


use base qw(Net::Server::PreFork);
use Config::IniFiles;
use Getopt::Long;
use Sys::Syslog;

use smradius::version;
use smradius::constants;
use smradius::logging;
use smradius::config;
Nigel Kukard's avatar
Nigel Kukard committed
use smradius::dbilayer;
use smradius::cache;
use smradius::util;
use smradius::attributes;
Nigel Kukard's avatar
Nigel Kukard committed

use Radius::Packet;

use Data::Dumper;



# Override configuration
sub configure {
	my ($self,$defaults) = @_;
	my $server = $self->{'server'};


	# If we hit a hash, add the config vars to the server
	if (defined($defaults)) {
		foreach my $item (keys %{$defaults}) {
			$server->{$item} = $defaults->{$item};
		}
		return;
	}

	# Set defaults
	my $cfg;
	$cfg->{'config_file'} = "/etc/smradiusd.conf";

	$server->{'timeout'} = 120;
	$server->{'background'} = "yes";
	$server->{'pid_file'} = "/var/run/smradiusd.pid";
	$server->{'log_level'} = 2;
	$server->{'log_file'} = "/var/log/smradiusd.log";

	$server->{'host'} = "*";
	$server->{'port'} = [ 1812, 1813 ];
	$server->{'proto'} = 'udp';
			
	$server->{'min_servers'} = 4;
	$server->{'min_spare_servers'} = 4;
	$server->{'max_spare_servers'} = 12;
	$server->{'max_servers'} = 25;
	$server->{'max_requests'} = 1000;

	# Parse command line params
	my $cmdline;
	%{$cmdline} = ();
	GetOptions(
			\%{$cmdline},
			"help",
			"config:s",
			"debug",
			"fg",
	) or die "Error parsing commandline arguments";
	
	# Check for some args
	if ($cmdline->{'help'}) {
		$self->displayHelp();
		exit 0;
	}
	if (defined($cmdline->{'config'}) && $cmdline->{'config'} ne "") {
		$cfg->{'config_file'} = $cmdline->{'config'};
	}

	# Check config file exists
	if (! -f $cfg->{'config_file'}) {
		die("No configuration file '".$cfg->{'config_file'}."' found!\n");
	}
	
	# Use config file, ignore case
	tie my %inifile, 'Config::IniFiles', (
			-file => $cfg->{'config_file'},
			-nocase => 1
	) or die "Failed to open config file '".$cfg->{'config_file'}."': $!";
	# Copy config
	my %config = %inifile;
	untie(%inifile);

	# Pull in params for the server
	my @server_params = (
			'log_level','log_file',
#			'port',   - We don't want to override this do we?
			'host',
			'cidr_allow', 'cidr_deny',
			'pid_file', 
			'user', 'group',
			'timeout',
			'background',
			'min_servers',     
			'min_spare_servers',
			'max_spare_servers',
			'max_servers',
			'max_requests',
	);
	foreach my $param (@server_params) {
		$server->{$param} = $config{'server'}{$param} if (defined($config{'server'}{$param}));
	}

	# Fix up these ...
	if (defined($server->{'cidr_allow'})) {
		my @lst = split(/,\s;/,$server->{'cidr_allow'});
		$server->{'cidr_allow'} = \@lst;
	}
	if (defined($server->{'cidr_deny'})) {
		my @lst = split(/,\s;/,$server->{'cidr_deny'});
		$server->{'cidr_deny'} = \@lst;
	}

	# Override
	if ($cmdline->{'debug'}) {
		$server->{'log_level'} = 4;
		$cfg->{'debug'} = 1;
	}

	# If we set on commandline for foreground, keep in foreground
	if ($cmdline->{'fg'} || (defined($config{'server'}{'background'}) && $config{'server'}{'background'} eq "no" )) {
		$server->{'background'} = undef;
		$server->{'log_file'} = undef;
	} else {
		$server->{'setsid'} = 1;
	}

	# Loop with logging detail
	if (defined($config{'server'}{'log_detail'})) {
		# Lets see what we have to enable
		foreach my $detail (split(/[,\s;]/,$config{'server'}{'log_detail'})) {
			$cfg->{'logging'}{$detail} = 1;
		}
	}
	
	#
	# Authentication plugins
	#
	my @auth_params = (
			'mechanisms',
			'users',
Nigel Kukard's avatar
Nigel Kukard committed
	);
	my $auth;
	foreach my $param (@auth_params) {
		$auth->{$param} = $config{'authentication'}{$param} if (defined($config{'authentication'}{$param}));
	}

	if (!defined($auth->{'mechanisms'})) {
		$self->log(LOG_ERR,"[SMRADIUS] Authentication configuration error: Mechanism plugins not found");
Nigel Kukard's avatar
Nigel Kukard committed
		exit 1;
	}
	if (!defined($auth->{'users'})) {
		$self->log(LOG_ERR,"[SMRADIUS] Authentication configuration error: Userdb plugins not found");
		exit 1;
	}

	#
	# Accounting plugins
	#
	my @acct_params = (
			'plugins',
	);
	my $acct;
	foreach my $param (@acct_params) {
		$acct->{$param} = $config{'accounting'}{$param} if (defined($config{'accounting'}{$param}));
	}

	if (!defined($acct->{'plugins'})) {
		$self->log(LOG_ERR,"[SMRADIUS] Accounting configuration error: Plugins not found");
		exit 1;
	}

	#
	# Feature plugins
	#
	my $features;
	$features->{'plugins'} = [ ];
	$features->{'plugins'} = $config{'features'}{'plugins'} if (defined($config{'features'}{'plugins'}));
Nigel Kukard's avatar
Nigel Kukard committed
	#
	# Dictionary configuration
	#
	my @dictionary_params = (
			'load',
	);
	my $dictionary;
	foreach my $param (@dictionary_params) {
		$dictionary->{$param} = $config{'dictionary'}{$param} if (defined($config{'dictionary'}{$param}));
	}

	if (!defined($dictionary->{'load'})) {
		$self->log(LOG_ERR,"[SMRADIUS] Dictionary configuration error: 'load' not found");
		exit 1;
	}
	# Split off dictionaries to load
	foreach my $fn (@{$dictionary->{'load'}}) {
		$fn =~ s/\s+//g;
	}
	
	$cfg->{'authentication'} = $auth;
	$cfg->{'dictionary'} = $dictionary;

	$cfg->{'plugins'} = [ 
		@{$auth->{'mechanisms'}},
		@{$auth->{'users'}},
		@{$acct->{'plugins'}},
		@{$features->{'plugins'}} 
Nigel Kukard's avatar
Nigel Kukard committed

	# Clean up plugins
	foreach my $plugin (@{$cfg->{'plugins'}}) {
		$plugin =~ s/\s+//g;
	}

Nigel Kukard's avatar
Nigel Kukard committed
	# Save our config and stuff
	$self->{'config'} = $cfg;
	$self->{'cmdline'} = $cmdline;
	$self->{'inifile'} = \%config;
}



# Run straight after ->run
sub post_configure_hook {
	my $self = shift;
	my $config = $self->{'config'};


	# Init config
	$self->log(LOG_NOTICE,"[SMRADIUS] Initializing configuration...");
	smradius::config::Init($self);
	$self->log(LOG_NOTICE,"[SMRADIUS] Configuration initialized.");

Nigel Kukard's avatar
Nigel Kukard committed
	# Load dictionaries
	$self->log(LOG_NOTICE,"[SMRADIUS] Initializing dictionaries...");
	my $dict = new Radius::Dictionary;
	foreach my $fn (@{$config->{'dictionary'}->{'load'}}) {
		# Load dictionary
		if (!$dict->readfile($fn)) {
			$self->log(LOG_WARN,"[SMRADIUS] Failed to load dictionary '$fn': $!");
		}
		$self->log(LOG_DEBUG,"[SMRADIUS] Loaded plugin '$fn'.");
	}
	$self->log(LOG_NOTICE,"[SMRADIUS] Dictionaries initialized.");
	# Store the dictionary
	$self->{'radius'}->{'dictionary'} = $dict;

	$self->log(LOG_NOTICE,"[SMRADIUS] Initializing modules...");
Nigel Kukard's avatar
Nigel Kukard committed
	# Load plugins
	foreach my $plugin (@{$config->{'plugins'}}) {
		# Load plugin
		my $res = eval("
			use $plugin;
			plugin_register(\$self,\"$plugin\",\$${plugin}::pluginInfo);
		");
		if ($@ || (defined($res) && $res != 0)) {
			$self->log(LOG_WARN,"[SMRADIUS] Error loading plugin $plugin ($@)");
Nigel Kukard's avatar
Nigel Kukard committed
		} else {
			$self->log(LOG_DEBUG,"[SMRADIUS] Plugin '$plugin' loaded.");
Nigel Kukard's avatar
Nigel Kukard committed
		}
	}
	$self->log(LOG_NOTICE,"[SMRADIUS] Plugins initialized.");
Nigel Kukard's avatar
Nigel Kukard committed

	$self->log(LOG_NOTICE,"[SMRADIUS] Initializing system modules.");
	# Init caching engine
#	smradius::cache::Init($self);
	$self->log(LOG_NOTICE,"[SMRADIUS] System modules initialized.");
	
}



# Register plugin info
sub plugin_register {
	my ($self,$plugin,$info) = @_;


	# If no info, return
	if (!defined($info)) {
		print(STDERR "WARNING: Plugin info not found for plugin => $plugin\n");
		return -1;
	}

	# Set real module name & save
	$info->{'Module'} = $plugin;
	push(@{$self->{'plugins'}},$info);

	# If we should, init the module
	if (defined($info->{'Init'})) {
		$info->{'Init'}($self);
	}

Nigel Kukard's avatar
Nigel Kukard committed
	return 0;
}


# Initialize child
sub child_init_hook
{
	my $self = shift;
	my $config = $self->{'config'};
Nigel Kukard's avatar
Nigel Kukard committed

	
	$self->SUPER::child_init_hook();
	
Nigel Kukard's avatar
Nigel Kukard committed
	$self->log(LOG_DEBUG,"[SMRADIUS] Starting up caching engine");
	smradius::cache::connect($self);
	# Do we need database support?
	if ($self->{'smradius'}->{'database'}->{'enabled'}) {
		# This is the database connection timestamp, if we connect, it resets to 0
		# if not its used to check if we must kill the child and try a reconnect
		$self->{'client'}->{'dbh_status'} = time();

		# Init core database support
		$self->{'client'}->{'dbh'} = smradius::dbilayer::Init($self);
		if (defined($self->{'client'}->{'dbh'})) {
			# Check if we succeeded
			if (!($self->{'client'}->{'dbh'}->connect())) {
Nigel Kukard's avatar
Nigel Kukard committed
			# If we succeeded, record OK
				$self->{'client'}->{'dbh_status'} = 0;
			} else {
				$self->log(LOG_WARN,"[SMRADIUS] Failed to connect to database: ".$self->{'client'}->{'dbh'}->Error()." ($$)");
			}
Nigel Kukard's avatar
Nigel Kukard committed
		} else {
			$self->log(LOG_WARN,"[SMRADIUS] Failed to Initialize: ".smradius::dbilayer::internalErr()." ($$)");
Nigel Kukard's avatar
Nigel Kukard committed
		}
	}
Nigel Kukard's avatar
Nigel Kukard committed
	
}



# Destroy the child
sub child_finish_hook {
	my $self = shift;
	my $server = $self->{'server'};

	$self->SUPER::child_finish_hook();
	
Nigel Kukard's avatar
Nigel Kukard committed
	$self->log(LOG_DEBUG,"[SMRADIUS] Shutting down caching engine ($$)");
	smradius::cache::disconnect($self);
Nigel Kukard's avatar
Nigel Kukard committed
}


# Process requests we get
sub process_request {
	my $self = shift;
	my $server = $self->{'server'};
	my $client = $self->{'client'};
Nigel Kukard's avatar
Nigel Kukard committed
	my $log = defined($server->{'config'}{'logging'}{'modules'});


	# Grab packet
	my $udp_packet = $server->{'udp_data'};

	# Check min size
	if (length($udp_packet) < 18)
	{
		$self->log(LOG_WARN, "[SMRADIUS] Packet too short - Ignoring");
		return;
	}

	# Parse packet
	my $pkt = new Radius::Packet($self->{'radius'}->{'dictionary'},$udp_packet);

	# VERIFY SOURCE SERVER
	$self->log(LOG_DEBUG,"[SMRADIUS] Packet From = > ".$server->{'peeraddr'});
Nigel Kukard's avatar
Nigel Kukard committed

	# Check if we got connected, if not ... bypass
	if ($self->{'client'}->{'dbh_status'} > 0) {
		my $action;

		$self->log(LOG_WARN,"[SMRADIUS] Client in BYPASS mode due to DB connection failure!");
		# Check bypass mode
		if (!defined($self->{'inifile'}{'database'}{'bypass_mode'})) {
			$self->log(LOG_ERR,"[SMRADIUS] No bypass_mode specified for failed database connections, defaulting to tempfail");
			$action = "tempfail";
		# Check for "tempfail"
		} elsif (lc($self->{'inifile'}{'database'}{'bypass_mode'}) eq "tempfail") {
		# And for "bypass"
		} elsif (lc($self->{'inifile'}{'database'}{'bypass_mode'}) eq "pass") {
		}
		
		# Check if we need to reconnect or not
		my $timeout = $self->{'inifile'}{'database'}{'bypass_timeout'};
		if (!defined($timeout)) {
			$self->log(LOG_ERR,"[SMRADIUS] No bypass_timeout specified for failed database connections, defaulting to 120s");
			$timeout = 120;
		}
		# Get time left
		my $timepassed = time() - $self->{'client'}->{'dbh_status'};
		# Then check...
		if ($timepassed >= $timeout) {
			$self->log(LOG_NOTICE,"[SMRADIUS] Client BYPASS timeout exceeded, reconnecting...");
			exit 0;
		} else {
			$self->log(LOG_NOTICE,"[SMRADIUS] Client still in BYPASS mode, ".( $timeout - $timepassed )."s left till next reconnect");
			return;
		}
	}

	# Setup database handle
	smradius::dblayer::setHandle($self->{'client'}->{'dbh'});


#LOGIN
#Service-Type:        Login-User
#User-Name:           joe
#User-Password:       \x{d3}\x{df}\x{10}\x{8c}\x{a0}r.\x{fd}=\x{ff}\x{96}\x{a}\x{86}\x{91}\x{e}c
#Calling-Station-Id:  10.254.254.242
#NAS-Identifier:      lbsd-test
#NAS-IP-Address:      10.254.254.239

#PPPOE:
#Service-Type:        Framed-User
#Framed-Protocol:     PPP
#NAS-Port:            19
#NAS-Port-Type:       Ethernet
#User-Name:           nigel
#Calling-Station-Id:  00:E0:4D:2A:72:35
#Called-Station-Id:   pppoe-24
#NAS-Port-Id:         ether1
#NAS-Identifier:      lbsd-test
#NAS-IP-Address:      10.254.254.239



#
# User Authentication
#

# Authentication
#a. SELECT ID, Password FROM Users WHERE Username = %u
# Optional Items:
# 	'Disabled' - Indicates the user is disabled
#
# Save the query result, so we can use it as macros....  ${user.<column name>}  below...


#
# Authorization: Attribute checks
#

# User attributes
#b. SELECT Attribute, OP, Value FROM UserAttributes WHERE UserID = ${user.id}

# Attribute groups
Nigel Kukard's avatar
Nigel Kukard committed
#c. SELECT Group FROM UsersToGroups WHERE UserID = ${user.id}
# Save the query result, so we can use it as macros... ${group.<column name>} below...

# Group attributes
#d. SELECT Attribute, OP, Value FROM GroupAttributes WHERE GroupID = ${group.id}
# Loop with groups and do the query ...

#
# Authentication procedure
#

# On user AUTH ....
Nigel Kukard's avatar
Nigel Kukard committed
#1. Execute query (a), set query result in 'user' hash
#	- Check 'disabled' parameter
#2. Run past plugins - check if we authenticate
Nigel Kukard's avatar
Nigel Kukard committed
#3. Pull in query (c), loop with groups for query (d)
#4. Merge in query (b)
#5. Check attributes that need checking
#	- reject if fail
#6. Return attributes that need to be returned


Nigel Kukard's avatar
Nigel Kukard committed
# find user
# get user
#	- User
#		- Password
#			{mech}data
#		- Data
#			(additional columns from table)
#		- Attributes (array)
#			Attribute,OP,Value
#	- Group (array)
#		- Data
#			(additional columns from table)
#		- Attributes
#			Attribute,OP,Value
# try authenticate
# check attribs
	# Main user hash with everything in
	my $user;
	# UserDB module if we using/need it
	my $userdb;

	# Common stuff for multiple codes....
	if ($pkt->code eq "Accounting-Request" || $pkt->code eq "Access-Request") {
		# Set username
		$user->{'Username'} = $pkt->attr('User-Name');
		
		#
		# FIND USER
		#

		# Loop with modules to try find user
		foreach my $module (@{$self->{'plugins'}}) {
			# Try find user
			if ($module->{'User_find'}) {
				$self->log(LOG_INFO,"[SMRADIUS] FIND: Trying plugin '".$module->{'Name'}."' for username '".$user->{'Username'}."'");
				my ($res,$userdb_data) = $module->{'User_find'}($self,$user,$pkt);

				# Check result
				if (!defined($res)) {
					$self->log(LOG_DEBUG,"[SMRADIUS] FIND: Error with plugin '".$module->{'Name'}."'");

				# Check if we skipping this plugin
				} elsif ($res == MOD_RES_SKIP) {
					$self->log(LOG_DEBUG,"[SMRADIUS] FIND: Skipping '".$module->{'Name'}."'");

				# Check if we got a positive result back
				} elsif ($res == MOD_RES_ACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] FIND: Username found with '".$module->{'Name'}."'");
					$userdb = $module;
					$user->{'_UserDB_Data'} = $userdb_data;
					last;

				# Or a negative result
				} elsif ($res == MOD_RES_NACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] FIND: Username not found with '".$module->{'Name'}."'");
					last;

				}
			}
		}
	}

Nigel Kukard's avatar
Nigel Kukard committed
	# Is this an accounting request
	if ($pkt->code eq "Accounting-Request") {

		$self->log(LOG_DEBUG,"[SMRADIUS] Accounting Request Packet");
Nigel Kukard's avatar
Nigel Kukard committed

		#
		# GET USER
		#

		# Get user data
		if (defined($userdb) && defined($userdb->{'User_get'})) {
			my $res = $userdb->{'User_get'}($self,$user,$pkt);

			# Check result
			if (defined($res) && ref($res) eq "HASH") {
				# We're only after the attributes here
Robert Anderson's avatar
Robert Anderson committed
				$user->{'Attributes'} = $res;
		# Loop with modules to try something that handles accounting
		foreach my $module (@{$self->{'plugins'}}) {
			# Try find user
			if ($module->{'Accounting_log'}) {
				$self->log(LOG_INFO,"[SMRADIUS] ACCT: Trying plugin '".$module->{'Name'}."'");
				my $res = $module->{'Accounting_log'}($self,$user,$pkt);

				# Check result
				if (!defined($res)) {
					$self->log(LOG_DEBUG,"[SMRADIUS] ACCT: Error with plugin '".$module->{'Name'}."'");

				# Check if we skipping this plugin
				} elsif ($res == MOD_RES_SKIP) {
					$self->log(LOG_DEBUG,"[SMRADIUS] ACCT: Skipping '".$module->{'Name'}."'");

				# Check if we got a positive result back
				} elsif ($res == MOD_RES_ACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] ACCT: Accounting logged using '".$module->{'Name'}."'");

				# Check if we got a negative result back
				} elsif ($res == MOD_RES_NACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] ACCT: Accounting NOT LOGGED using '".$module->{'Name'}."'");
		# Tell the NAS we got its packet	
		my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'});
	   	$resp->set_code('Accounting-Response');
		$resp->set_identifier($pkt->identifier);
		$resp->set_authenticator($pkt->authenticator);
		$udp_packet = auth_resp($resp->pack, "test");
		$server->{'client'}->send($udp_packet);
		# Loop with features that have post-authentication hooks
		foreach my $module (@{$self->{'plugins'}}) {
			# Try authenticate
			if ($module->{'Feature_Post-Accounting_hook'}) {
				$self->log(LOG_INFO,"[SMRADIUS] POST-ACCT: Trying plugin '".$module->{'Name'}."' for '".$user->{'Username'}."'");
				my $res = $module->{'Feature_Post-Accounting_hook'}($self,$user,$pkt);

				# Check result
				if (!defined($res)) {
					$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Error with plugin '".$module->{'Name'}."'");

				# Check if we skipping this plugin
				} elsif ($res == MOD_RES_SKIP) {
					$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Skipping '".$module->{'Name'}."'");

				# Check if we got a positive result back
				} elsif ($res == MOD_RES_ACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] POST-ACCT: Passed authenticated by '".$module->{'Name'}."'");

				# Or a negative result
				} elsif ($res == MOD_RES_NACK) {
					$self->log(LOG_NOTICE,"[SMRADIUS] POST-ACCT: Failed authentication by '".$module->{'Name'}."'");
					#$authenticated = 0;
					# Do we want to run the other features ??
					#last;
				}
			}
		}

Nigel Kukard's avatar
Nigel Kukard committed
	# Or maybe a access request
	} elsif ($pkt->code eq "Access-Request") {
	
	
		$self->log(LOG_DEBUG,"[SMRADIUS] Access Request Packet");
		$self->log(LOG_DEBUG,"[SMRADIUS] Packet: ".$pkt->dump);
	
		# Authentication variables
Nigel Kukard's avatar
Nigel Kukard committed
		my $authenticated = 0;
		my $mechanism;
		# Authorization variables
		my $authorized = 1;
Nigel Kukard's avatar
Nigel Kukard committed


		# If no user is found, bork out ...
		if (!defined($userdb)) {
			$self->log(LOG_INFO,"[SMRADIUS] FIND: No plugin found for username '".$user->{'Username'}."'");
Nigel Kukard's avatar
Nigel Kukard committed
			goto CHECK_RESULT;
		}

		#
		# GET USER
		#

		# Get user data
		if ($userdb->{'User_get'}) {
			my $res = $userdb->{'User_get'}($self,$user,$pkt);
Nigel Kukard's avatar
Nigel Kukard committed

			# Check result
			if (!defined($res) || ref($res) ne "HASH") {
				$self->log(LOG_WARN,"[SMRADIUS] GET: No data returned from '".$userdb->{'Name'}."' for username '".$user->{'Username'}."'");
Nigel Kukard's avatar
Nigel Kukard committed
				goto CHECK_RESULT;
			}
			# Setup user dataw
Robert Anderson's avatar
Robert Anderson committed
			$user->{'Attributes'} = $res;
		} else {
			$self->log(LOG_INFO,"[SMRADIUS] GET: No 'User_get' function available for module '".$userdb->{'Name'}."'");

			goto CHECK_RESULT;
Nigel Kukard's avatar
Nigel Kukard committed
		}

		#
		# AUTHENTICATE USER
		#
Nigel Kukard's avatar
Nigel Kukard committed

		# Loop with authentication modules
Nigel Kukard's avatar
Nigel Kukard committed
		foreach my $module (@{$self->{'plugins'}}) {
			# Try authenticate
			if ($module->{'Authentication_try'}) {
Nigel Kukard's avatar
Nigel Kukard committed
				$self->log(LOG_INFO,"[SMRADIUS] AUTH: Trying plugin '".$module->{'Name'}."' for '".$user->{'Username'}."'");
				my $res = $module->{'Authentication_try'}($self,$user,$pkt);
Nigel Kukard's avatar
Nigel Kukard committed

				# Check result
				if (!defined($res)) {
					$self->log(LOG_DEBUG,"[SMRADIUS] AUTH: Error with plugin '".$module->{'Name'}."'");
Nigel Kukard's avatar
Nigel Kukard committed

				# Check if we skipping this plugin
				} elsif ($res == MOD_RES_SKIP) {
Nigel Kukard's avatar
Nigel Kukard committed
					$self->log(LOG_DEBUG,"[SMRADIUS] AUTH: Skipping '".$module->{'Name'}."'");
Nigel Kukard's avatar
Nigel Kukard committed

				# Check if we got a positive result back
				} elsif ($res == MOD_RES_ACK) {
Nigel Kukard's avatar
Nigel Kukard committed
					$self->log(LOG_NOTICE,"[SMRADIUS] AUTH: Authenticated by '".$module->{'Name'}."'");
Nigel Kukard's avatar
Nigel Kukard committed
					$mechanism = $module;
					$authenticated = 1;
					last;

				# Or a negative result
				} elsif ($res == MOD_RES_NACK) {
Nigel Kukard's avatar
Nigel Kukard committed
					$self->log(LOG_NOTICE,"[SMRADIUS] AUTH: Failed authentication by '".$module->{'Name'}."'");
Nigel Kukard's avatar
Nigel Kukard committed
					$mechanism = $module;
					last;

				}
			}
		}

		# Loop with features that have post-authentication hooks
		if ($authenticated) {
			foreach my $module (@{$self->{'plugins'}}) {
				# Try authenticate
				if ($module->{'Feature_Post-Authentication_hook'}) {
					$self->log(LOG_INFO,"[SMRADIUS] POST-AUTH: Trying plugin '".$module->{'Name'}."' for '".$user->{'Username'}."'");
					my $res = $module->{'Feature_Post-Authentication_hook'}($self,$user,$pkt);

					# Check result
					if (!defined($res)) {
						$self->log(LOG_DEBUG,"[SMRADIUS] POST-AUTH: Error with plugin '".$module->{'Name'}."'");

					# Check if we skipping this plugin
					} elsif ($res == MOD_RES_SKIP) {
						$self->log(LOG_DEBUG,"[SMRADIUS] POST-AUTH: Skipping '".$module->{'Name'}."'");

					# Check if we got a positive result back
					} elsif ($res == MOD_RES_ACK) {
						$self->log(LOG_NOTICE,"[SMRADIUS] POST-AUTH: Passed authenticated by '".$module->{'Name'}."'");

					# Or a negative result
					} elsif ($res == MOD_RES_NACK) {
						$self->log(LOG_NOTICE,"[SMRADIUS] POST-AUTH: Failed authentication by '".$module->{'Name'}."'");
						$authenticated = 0;
						# Do we want to run the other features ??
						last;
					}
				}
			}
		}

Nigel Kukard's avatar
Nigel Kukard committed
		#
		# AUTHORIZE USER
		#

		# Build a list of our attributes in the packet
		my $authAttributes;
		foreach my $attr ($pkt->attributes) {
			$authAttributes->{$attr} = $pkt->rawattr($attr);
		}
		# Loop with attributes we got from the user
Robert Anderson's avatar
Robert Anderson committed
		foreach my $attrName (keys %{$user->{'Attributes'}}) {
			# Loop with operators
			foreach my $attrOp (keys %{$user->{'Attributes'}->{$attrName}}) {
				# Grab attribute
				my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
				# Check attribute against authorization attributes
				my $res = checkAttributeAuth($self,$authAttributes,$attr);
				if ($res == 0) {
					$authorized = 0;
					last;
				}
Robert Anderson's avatar
Robert Anderson committed
			# We don't want to process everyting if something doesn't match
			last if (!$authorized);
Nigel Kukard's avatar
Nigel Kukard committed

		# Check if we authenticated or not
		if ($authenticated && $authorized) {
			$self->log(LOG_DEBUG,"[SMRADIUS] Authenticated and authorized");

			my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'});
		 	$resp->set_code('Access-Accept');
			$resp->set_identifier($pkt->identifier);
			$resp->set_authenticator($pkt->authenticator);
Robert Anderson's avatar
Robert Anderson committed
			# Loop with attributes we got from the getReplyAttributes function, its a hash of arrays which are the values
			my %replyAttributes = ();
			foreach my $attrName (keys %{$user->{'Attributes'}}) {
				# Loop with operators
				foreach my $attrOp (keys %{$user->{'Attributes'}->{$attrName}}) {
					# Grab attribute
					my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
					# Add this to the reply attribute?
					getReplyAttribute($self,\%replyAttributes,$attr);
				}
			}
			# Loop with reply attributes
			foreach my $attrName (keys %replyAttributes) {
				# Loop with values
				foreach my $value (@{$replyAttributes{$attrName}}) {
					# Add each value
					$resp->set_attr($attrName,$value);
			$udp_packet = auth_resp($resp->pack, "test");
			$server->{'client'}->send($udp_packet);
Nigel Kukard's avatar
Nigel Kukard committed
CHECK_RESULT:
		# Check if found and authenticated
		if (!$authenticated || !$authorized) {
			$self->log(LOG_DEBUG,"[SMRADIUS] Authentication or authorization failure");

			my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'});
		 	$resp->set_code('Access-Reject');
			$resp->set_identifier($pkt->identifier);
			$resp->set_authenticator($pkt->authenticator);
			$udp_packet = auth_resp($resp->pack, "test");
			$server->{'client'}->send($udp_packet);
Nigel Kukard's avatar
Nigel Kukard committed
		}
Nigel Kukard's avatar
Nigel Kukard committed
	# We don't know how to handle this
	} else {
		$self->log(LOG_WARN,"[SMRADIUS] We cannot handle code: '".$pkt->code."'");
	}

return;
#	$pkt->dump;
#
#	# PAP
#	if ((my $rawPassword = $pkt->attr('User-Password'))) {
#
#
#		print(STDERR "RECEIVED\n");
#		print(STDERR "User-Pass: len = ".length($rawPassword).", hex = ".unpack("H*",$rawPassword)."\n");
#		print(STDERR "\n\n");
#
#		my $result = $pkt->password("test","User-Password");
#	
#		print(STDERR "CALC\n");
#		print(STDERR "Result   : len = ".length($result).", hex = ".unpack("H*",$result).", password = $result\n");
#
#	}
#
#	# CHAP
#	if ((my $rawChallenge = $pkt->attr('CHAP-Challenge')) && (my $rawPassword = $pkt->attr('CHAP-Password'))) {
#		print(STDERR "This is a CHAP challenge....\n");
#	
#		print(STDERR "RECEIVED\n");
#		print(STDERR "Challenge: len = ".length($rawChallenge).", hex = ".unpack("H*",$rawChallenge)."\n");
#		print(STDERR "Password : len = ".length($rawPassword).", hex = ".unpack("H*",$rawPassword)."\n");
#		print(STDERR "\n\n");
#
#		my $id = substr($rawPassword,0,1);
#		print(STDERR "ID: ".length($id).", hex = ".unpack("H*",$id)."\n");
#
#		my $result = encode_chap($id,$rawChallenge,"mytest");
#		
#		print(STDERR "CALC\n");
#		print(STDERR "Result   : len = ".length($result).", hex = ".unpack("H*",$result)."\n");
#		print(STDERR "\n\n");
#	}
#
#
#	# Is this a MSCHAP autehentication attempt?
#	if ((my $rawChallenge = $pkt->vsattr("311",'MS-CHAP-Challenge'))) {
#		print(STDERR "This is a MS-CHAP challenge....\n");
#
#		# MSCHAPv1
#		if (my $rawResponse = $pkt->vsattr("311",'MS-CHAP-Response')) {
#			my $challenge = @{$rawChallenge}[0];
#			my $response = substr(@{$rawResponse}[0],2);
#
#			print(STDERR "RECEIVED\n");
#			print(STDERR "Challenge: len = ".length($challenge).", hex = ".unpack("H*",$challenge)."\n");
#			print(STDERR "Reponse  : len = ".length($response).", hex = ".unpack("H*",$response)."\n");
#			print(STDERR "\n\n");
#
#
#
#			print(STDERR "CHOPPED OFFF!!\n");
##			my $peerChallenge = substr($response,0,16);
#			my $NtResponse = substr($response,24,24);
##			print(STDERR "Challenge: len = ".length($peerChallenge).", hex = ".unpack("H*",$peerChallenge)."\n");
#			print(STDERR "NTRespons: len = ".length($NtResponse).", hex = ".unpack("H*",$NtResponse)."\n");
#			print(STDERR "\n\n");
#
#			my $unipass = "mytest";
#			$unipass =~ s/(.)/$1\0/g; # convert ASCII to unicaode
#			my $username = "nigel";
#
#			print(STDERR "TEST\n");
##			my $ourChallenge = ChallengeHash($peerChallenge,$challenge,$username);
#			my $ourResponse = NtChallengeResponse($challenge,$unipass);
#			print(STDERR "Calculate: len = ".length($ourResponse).", hex = ".unpack("H*",$ourResponse)."\n");
#			print(STDERR "\n\n");
#
#
#		# MSCHAPv2
#		} elsif (my $rawResponse = $pkt->vsattr("311",'MS-CHAP2-Response')) {
#			my $challenge = @{$rawChallenge}[0];
#			my $response = substr(@{$rawResponse}[0],2);
#
#			print(STDERR "RECEIVED\n");
#			print(STDERR "Challenge: len = ".length($challenge).", hex = ".unpack("H*",$challenge)."\n");
#			print(STDERR "Reponse  : len = ".length($response).", hex = ".unpack("H*",$response)."\n");
#			print(STDERR "\n\n");
#
#
#
#			print(STDERR "CHOPPED OFFF!!\n");
Nigel Kukard's avatar
Nigel Kukard committed
#			my $peerChallenge = substr($response,0,16);
#			my $NtRespnse = substr($response,24,24);
Nigel Kukard's avatar
Nigel Kukard committed
#			print(STDERR "Challenge: len = ".length($peerChallenge).", hex = ".unpack("H*",$peerChallenge)."\n");
#			print(STDERR "NTRespons: len = ".length($NtRespnse).", hex = ".unpack("H*",$NtRespnse)."\n");
#			print(STDERR "\n\n");
#
#			my $unipass = "mytest";
#			$unipass =~ s/(.)/$1\0/g; # convert ASCII to unicaode
#			my $username = "nigel";
#
#			print(STDERR "TEST\n");
Nigel Kukard's avatar
Nigel Kukard committed
#			my $ourChallenge = ChallengeHash($peerChallenge,$challenge,$username);
#			my $ourResponse = NtChallengeResponse($ourChallenge,$unipass);
#			print(STDERR "Calculate: len = ".length($ourResponse).", hex = ".unpack("H*",$ourResponse)."\n");
#			print(STDERR "\n\n");
#
#
#
#		}
#	}
#
#
#
##	printf("GOT PACKET: user = %s/%s, nas-ip = %s, nas-port-type = %s, nas-port = %s, connect-info = %s, service-type = %s\n",
##		$pkt->attr('User-Name'), $pkt->password('test'),
##		$pkt->attr('NAS-IP-Address'),
##		$pkt->attr('NAS-Port-Type'),
##		$pkt->attr('NAS-Port'),
##		$pkt->attr('Connect-Info'),
##		$pkt->attr('Service-Type')
##	);
#
#
#	if ($pkt->code eq "Accounting-Request") {
#		my $resp = Radius::Packet->new($self->{'config'}->{'dictionary'});
#		$resp->set_code('Accounting-Response');
#		$resp->set_identifier($pkt->identifier);
#		$resp->set_authenticator($pkt->authenticator);
#		$udp_packet = auth_resp($resp->pack, "test");
#		$server->{'client'}->send($udp_packet);
#
#
#	} elsif ($pkt->code eq "Access-Request") {
#		my $resp = Radius::Packet->new($self->{'config'}->{'dictionary'});
#    	$resp->set_code('Access-Accept');
#	    $resp->set_identifier($pkt->identifier);
#	    $resp->set_authenticator($pkt->authenticator);
#	    $resp->set_attr('Framed-IP-Address' => "192.168.0.233");
#		$udp_packet = auth_resp($resp->pack, "test");
#		$server->{'client'}->send($udp_packet);
#	}
#
Nigel Kukard's avatar
Nigel Kukard committed

}



# Initialize child
sub server_exit
{
	my $self = shift;

	
	$self->log(LOG_DEBUG,"Destroying system modules.");
	# Destroy cache
#	cbp::cache::Destroy($self);
	$self->log(LOG_DEBUG,"System modules destroyed.");

	# Parent exit