Skip to content
Snippets Groups Projects
smradiusd 27.2 KiB
Newer Older
Nigel Kukard's avatar
Nigel Kukard committed
#!/usr/bin/perl
# Radius daemon
Nigel Kukard's avatar
Nigel Kukard committed
# Copyright (C) 2007-2009, AllWorldIT
Nigel Kukard's avatar
Nigel Kukard committed
# 
# 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
	smradius/modules/config
Nigel Kukard's avatar
Nigel Kukard committed
);

package radiusd;


use base qw(Net::Server::PreFork);
use Config::IniFiles;
use DateTime;
Nigel Kukard's avatar
Nigel Kukard committed
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;

Nigel Kukard's avatar
Nigel Kukard committed


# 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;
		}
	}
	
	#
	# System plugins
	#
	my @system_params = (
			'plugins',
	);
	my $system;
	foreach my $param (@system_params) {
		$system->{$param} = $config{'system'}{$param} if (defined($config{'system'}{$param}));
	}

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

Nigel Kukard's avatar
Nigel Kukard committed
	#
	# 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->{'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'}}, 
		@{$system->{'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

#PPTP
#Service-Type:        Framed-User
#Framed-Protocol:     PPP
#NAS-Port:            49
#NAS-Port-Type:       Virtual
#User-Name:           johnsmith
#Calling-Station-Id:  10.254.254.242
#Called-Station-Id:   10.254.254.239
#NAS-Identifier:      lbsd-test
#NAS-IP-Address:      10.254.254.239

	# Main user hash with everything in
	my $user;
	$user->{'ConfigAttributes'} = {};
	$user->{'ReplyAttributes'} = {};
	$user->{'ReplyVAttributes'} = {};
	# Private data
	$user->{'_Internal'} = {
		'Timestamp-Unix' => defined($pkt->rawattr('Event-Timestamp')) ? $pkt->rawattr('Event-Timestamp') : time()
	};
	my $eventTimestamp = DateTime->from_epoch( epoch => $user->{'_Internal'}->{'Timestamp-Unix'} );
	$user->{'_Internal'}->{'Timestamp'} = $eventTimestamp->strftime('%Y-%m-%d %H:%M:%S');


	foreach my $module (@{$self->{'plugins'}}) {
		# Try find config attribute
		if ($module->{'Config_get'}) {

			# Get result from config module
			$self->log(LOG_INFO,"[SMRADIUS] CONFIG: Trying plugin '".$module->{'Name'}."' for incoming connection");
			my $res = $module->{'Config_get'}($self,$user,$pkt);

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

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

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

			# Check if we got a negative result back
			} elsif ($res == MOD_RES_NACK) {
				$self->log(LOG_NOTICE,"[SMRADIUS] CONFIG: Configuration problem when using '".$module->{'Name'}."'");
	# FIXME - need secret
	# FIXME - need acl list
	# 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, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));
		$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 post accounting hook by '".$module->{'Name'}."'");

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

		$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying to disconnect user...");

		my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'});

	 	$resp->set_code('Disconnect-Request');
		my $id = $$ & 0xff;
		$resp->set_identifier( $id );

		$resp->set_attr('User-Name',$pkt->attr('User-Name'));
		$resp->set_attr('Framed-IP-Address',$pkt->attr('Framed-IP-Address'));
		$resp->set_attr('NAS-IP-Address',$pkt->attr('NAS-IP-Address'));

		$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));

		# Create socket to send packet out on
		my $podServer = "10.254.254.239";
		my $podServerPort = "1700";
		my $podServerTimeout = "10";  # 10 second timeout	
		my $podSock = new IO::Socket::INET(
				PeerAddr => $podServer,
				PeerPort => $podServerPort,
				Type => SOCK_DGRAM,
				Proto => 'udp',
				TimeOut => $podServerTimeout,
		) or return $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to create socket to send POD on: $!");
		$podSock->send ($udp_packet) || return $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to send data on socket: $!");

		# Once sent, we need to get a response back
		my $sh = new IO::Select($podSock) or return $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to select data on socket: $!");
		$sh->can_read($podServerTimeout) or return $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to receive data on socket: $!");

		my $data;
		$podSock->recv($data, 65536) or return $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Receive data failed: $!");
#		my @stuff = unpack('C C n a16 a*', $data);
#		$self->log(LOG_DEBUG,"STUFF: ".Dumper(\@stuff));
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");
	
		# 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 = checkAuthAttribute($self,$authAttributes,$attr);
Robert Anderson's avatar
Robert Anderson committed
				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 = %{ $user->{'ReplyAttributes'} };
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};
					# Add this to the reply attribute?
					setReplyAttribute($self,\%replyAttributes,$attr);
Robert Anderson's avatar
Robert Anderson committed
				}
			}
			# 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);
			# Loop with vendor reply attributes
			my %replyVAttributes = %{ $user->{'ReplyVAttributes'} };
			foreach my $vendor (keys %replyVAttributes) {
				# Loop with operators
				foreach my $attrName (keys %{$replyVAttributes{$vendor}}) {
					# Add each value
					foreach my $value (@{$replyVAttributes{$vendor}->{$attrName}}) {
						$resp->set_vsattr($vendor,$attrName,$value);
					}
				}
			}
			$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));
			$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, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));
			$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."'");
	}

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
	$self->SUPER::server_exit();
}



# Slightly better logging
sub log
{
	my ($self,$level,$msg,@args) = @_;

	# Check log level and set text
	my $logtxt = "UNKNOWN";
	if ($level == LOG_DEBUG) {
		$logtxt = "DEBUG";
	} elsif ($level == LOG_INFO) {
		$logtxt = "INFO";
	} elsif ($level == LOG_NOTICE) {
		$logtxt = "NOTICE";
	} elsif ($level == LOG_WARN) {
		$logtxt = "WARNING";
	} elsif ($level == LOG_ERR) {
		$logtxt = "ERROR";
	} 

	# Parse message nicely
	if ($msg =~ /^(\[[^\]]+\]) (.*)/s) {
		$msg = "$1 $logtxt: $2";
	} else {
		$msg = "[CORE] $logtxt: $msg";
	}

	$self->SUPER::log($level,"[".$self->log_time." - $$] $msg",@args);
}



# Display help
sub displayHelp {
Nigel Kukard's avatar
Nigel Kukard committed
	print(STDERR "SMRadius v".VERSION." - Copyright (c) 2007-2009, AllWorldIT\n");
Nigel Kukard's avatar
Nigel Kukard committed

	print(STDERR<<EOF);

Usage: $0 [args]
    --config=<file>        Configuration file
    --debug                Put into debug mode
    --fg                   Don't go into background

EOF
}




__PACKAGE__->run;


1;
# vim: ts=4