#!/usr/bin/perl -w
#
##
#
# ASCFILE - Openradius module that can read legacy-style clients, users,
# realms, hints and huntgroups files
#
##
#
# Usage: ascfile [-d] file...
# ascfile -h
#
# -d increases verbosity on stderr and allows module to run standalone
#
# Multiple files named on the command line are effectively concatenated.
#
# Comments are started by a # and ended by a newline.
#
# A record consists of a key, followed by whitespace, followed by a series of
# A/V items, each separated by a comma or whitespace. If additional A/V items
# follow on subsequent lines, those lines must be indented with whitespace.
#
# Multiple entries having the same key are effectively concatenated.
#
# An A/V item can either be a bare value or a full A/V pair.
#
# A bare value will get the attribute 'int' if the value seems numeric, 'ip'
# if the value is an IP address, and 'str' otherwise, to form complete pairs.
#
# The module uses the first 'str' attribute from the request as its key, and
# *returns all pairs* associated with that key. That means it doesn't handle
# check items the way you're used to; it doesn't match them first but just
# returns them.
#
# If you want to compare items, and possibly use another key upon a mismatch,
# you can add a rule to do that in the behaviour file. Otherwise, you should
# rewrite eg. a series of DEFAULT items, each with a check item like
# Service-Type = Login, Framed, etc., as keyed on the service type instead.
#
# This module could have been written to deal with check items the way you're
# used to - but I feel that the type of logic that is needed there shouldn't
# be placed outside the behaviour file. And in most cases, the files will be
# oriented on a single key anyway, so they can be easily rewritten.
#
# If you'd really need to have two keys, a users-type of file isn't the best
# solution anyway, as you'd get n x m entries that will probably all be quite
# similar. Better split the information across two tables. But if you
# absolutely cannot, you can merge your check items all into the key, like
# steve:23555443:3, and call the module from the behaviour file like this:
# Ascfile(str=User-Name . ":" . Calling-Station-Id . ":" . Service-Type)
#
# After answering each request, the module checks if any of the files are
# newer than when they were last read, and if so, it rereads all.
# (that's not implemented yet).
#
##
#
# 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/10/16 - EvB - First version
# 2002/08/30 - Brian Candler - changed parsing so that any line which does
# not start with whitespace is taken as a new key.
#
##
########
# USES #
########
use Getopt::Long;
###########
# GLOBALS #
###########
$debug = 0;
$single = 0;
#############
# FUNCTIONS #
#############
# Readfile
#
# Returns a reference to a hash keyed on the file key, of references to arrays
# of pairs that are associated with that file key. Accepts a list of files as
# the argument.
sub readfile {
my %entries;
foreach my $file (@_) {
open(FH, $file) or
die("ascfile[$$]: ERROR: Could not open $file: $!\n");
$debug > 1 and print STDERR "READING FILE '$file'\n";
my $key;
while(<FH>) {
# Skip comment and blank lines
next if /^\s*(#|$)/;
# Extract optional key from beginning of line
if (s/^\r?([^\s]+)//) {
$key = $1;
$entries{$key} = [] unless exists $entries{$key};
$debug > 1 and print STDERR "---$key---\n";
}
# Process the rest as items, each of which is either a real
# A/V-pair or a bare value, and parsed as three parts:
# 1. an optional attribute spec. as in [A-Za-z0-9-]+\s*=\s*
# 2. - an (hexa)decimal number, as in [A-Fa-f0-9]+
# (we add int= if no attribute specified)
# - or an IP address as in \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
# (we add ip= if no attribute specified)
# - or a quoted string, as in "([^"]*|\\")*"
# (we add str= if no attribute specified)
# - or a bare string, as in [^\s,]+
# (we add str= if no attribute specified)
# 3. mandatory whitespace, comma, or end-of-record.
# (this is important to make sure the pairs are separated,
# so a bare string 'Deadbody' won't generate two pairs:
# a hex number int=deadb and a string str=ody).
while(s/^\s*(
([A-Za-z0-9:-]+)\s*=\s* # 1. attr. spec.
)?
(
([A-Fa-f0-9]+)| # 2. hex nr.
(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})| # or IP addr
("([^"]|\\")*[^\\]")| # or quoted
([^\s,#]+) # or bare str
)
(
[\s]+|,|$ # 3. end of item
)//x) {
# Establish the full pair
my $pair=($2 or $4 && 'int' or $5 && 'ip' or 'str') .
" = $3";
$debug > 1 and print STDERR "\t$pair\n";
# Add to this key's array of pairs in hash
push @{$entries{$key}}, $pair;
}
# ignore trailing space/comment
next if /^\s*(#|$)/;
warn("ascfile: $file line $.: Unable to parse (ignored): $_");
}
close(FH);
}
if ($debug) {
print STDERR "ascfile[$$]: In memory after reading @_:\n";
foreach my $key (keys %entries) {
print STDERR "---$key---\n";
foreach my $pair (@{$entries{$key}}) {
print STDERR "\t$pair\n";
}
}
}
# Return ref to the created hash of array refs
return \%entries;
}
########
# MAIN #
########
# Get options
Getopt::Long::Configure("bundling");
GetOptions("h" => \$usage,
"s" => \$single,
"d+" => \$debug);
if ($usage) { die("Usage: ascfile [-d] file...\n ascfile -h\n"); }
if ($single) { warn("ascfile: warning: -s flag no longer has any effect\n"); }
# Check that rest of command line is not empty
unless (@ARGV) { die("ascfile: ERROR: no file(s) specified!\n"); }
# Check that we're running under OpenRADIUS, interface version 1
unless ($debug ||
$ENV{'RADIUSINTERFACEVERSION'} &&
$ENV{'RADIUSINTERFACEVERSION'} == 1) {
die("ascfile: ERROR: not running under OpenRADIUS, interface v1!\n");
}
# Read the files mentioned on the command line
my $entries = &readfile(@ARGV);
# Set record separator to empty line and loop on input.
$/ = "\n\n";
$| = 1; # Important - we're outputting to a pipe
while(<STDIN>) {
# Parse pairs from server's request message
chomp;
/^\s*str\s*=\s*"(.*)"\s*$/m and my $str = $1;
# Debugging
$debug and print STDERR "ascfile[$$]: got request: $_\n" .
"ascfile[$$]: last str in request: $str\n";
# Output array of pairs for this key if we got one and it's in the hash
if (defined $str && defined $entries->{$str}) {
foreach my $pair (@{$entries->{$str}}) {
print "$pair\n";
}
print "int=1\n";
}
else { print "int=0\n"; }
# Done
print "\n";
undef $str;
}
syntax highlighted by Code2HTML, v. 0.9.1