1#! /usr/bin/perl 2# 3# 4# $Id: DBStore.pm 109 2009-10-17 22:00:16Z lem $ 5 6package Net::Radius::Server::DBStore; 7 8use 5.010; 9use strict; 10use warnings; 11 12use Storable qw/freeze/; 13 14use Net::Radius::Server::Base qw/:set/; 15use base 'Net::Radius::Server::Base'; 16__PACKAGE__->mk_accessors(qw/key_attrs param store result sync 17 pre_store_hook single frozen hashref 18 internal_tie/); 19our $VERSION = do { sprintf "%0.3f", 1+(q$Revision: 109 $ =~ /\d+/g)[0]/1000 }; 20 21sub mk 22{ 23 my $class = shift; 24 die "->mk() cannot have arguments when in object-method mode\n" 25 if ref($class) and $class->isa('UNIVERSAL') and @_; 26 27 my $self = $class; 28 29 if (@_) 30 { 31 $self = $class->new(@_); 32 die "Failed to create new object\n" unless $self; 33 } 34 35 die "->mk() cannot proceed with no valid param defined\n" 36 unless ref($self->param) eq 'ARRAY'; 37 38 # Enforce default values 39 40 $self->frozen(1) unless defined $self->frozen(); 41 $self->single(1) unless defined $self->single(); 42 $self->internal_tie(1) unless defined $self->internal_tie(); 43 44 $self->key_attrs([ 'NAS-IP-Address', '|', 'Acct-Session-Id' ]) 45 unless $self->key_attrs(); 46 47 $self->store([ qw/ packet peer_addr peer_host peer_port port /]) 48 unless $self->store(); 49 50 $self->sync(1) 51 unless defined $self->sync(); 52 53 $self->log_level(1) 54 unless defined $self->log_level(); 55 56 # Create the tied hash that we will be passing into the actual method. 57 58 my ($db, %hash); 59 my ($c, @params) = (@{$self->param}); 60 61 $self->hashref(\%hash) unless $self->hashref; 62 63 $self->log(2, "Tying to class '" . $c . "'"); 64 $self->log(3, "Tie parameters are " . join(', ', @params)); 65 if ($self->internal_tie) 66 { 67 eval { $db = tie %{$self->hashref}, $c, @params }; 68 69 die "->mk() unable to tie: $!" unless $db; 70 die "->mk() problem during tie: $@" if $@; 71 } 72 else 73 { 74 $self->log(2, "Not tying because ->internal_tie is true"); 75 } 76 77 return sub { $self->_do_tie( $db, $self->hashref, @_ ) }; 78} 79 80# Convert a scalar into the corresponding Radius attribute in 81# $req. Will return non-matched scalars, to be used as delimiters in 82# the resulting key. 83sub _k 84{ 85 my ($self, $db, $rhash, $r_data, $req, $attr) = @_; 86 my $v = undef; 87 88 if (ref($attr) eq 'ARRAY') 89 { 90 $v = $req->vsattr(@$attr); 91 return $v->[0] if ref($v) eq 'ARRAY'; 92 return $v if defined $v; 93 return ''; 94 } 95 elsif (ref($attr) eq 'CODE') 96 { 97 return $attr->($self, $db, $rhash, $r_data, $req); 98 } 99 else 100 { 101 $v = $req->attr($attr); 102 return $v if defined $v; 103 } 104 105 return $attr; 106} 107 108sub _do_tie 109{ 110 my $self = shift; 111 my $db = shift; 112 my $rhash = shift; 113 my $r_data = shift; 114 115 my $req = $r_data->{request}; 116 117 $self->log(2, 'Storing data'); 118 $self->log(4, "self=$self rhash=$rhash r_data=$r_data"); 119 120 # Find the key to store 121 my $key = join('', (map { $self->_k($db, $rhash, $r_data, $req, $_) } 122 @{$self->key_attrs})); 123 $self->log(4, 'Storing data using key "' . $key . '"'); 124 125 # Invoke hook, if available 126 my $f = undef; 127 if ($f = $self->pre_store_hook() 128 and ref($f) eq 'CODE') 129 { 130 $self->log(3, 'Invoking pre_store_hook'); 131 # Note that the pre_store_hook could change object's config... 132 $f->($self, $db, $rhash, $r_data, $req, $key); 133 } 134 else 135 { 136 $self->log(4, 'no pre_store_hook'); 137 } 138 139 # Find what to store 140 my @store = @{$self->store}; 141 $self->log(4, 'Storing the following items: ' . join(', ', @store)); 142 my %data = map { $_ => $r_data->{$_} } @store; 143 144 if ($self->single) 145 { 146 $self->log(4, "Single Store $key: ", \%data); 147 $rhash->{$key} = ($self->frozen ? freeze \%data : \%data); 148 } 149 else 150 { 151 while (my ($k, $v) = each %data) 152 { 153 $self->log(4, "Non-Single Store $key->$k: $v"); 154 $rhash->{$key}->{$k} = ($self->frozen ? freeze $v : $v) 155 } 156 } 157 $self->log(4, "tuple contains: " . $rhash->{$key} // 'undef'); 158 159 # Force sync writes 160 $db->db_sync if $db and $self->sync and $db->can('db_sync'); 161 162 if ($self->can('result') and exists $self->{result}) 163 { 164 my $r = $self->result; 165 $self->log(3, "Returning $r"); 166 return $r; 167 } 168 169 $self->log(3, "Returning CONTINUE by default"); 170 return NRS_SET_CONTINUE; 171} 172 17342; 174 175__END__ 176 177=head1 NAME 178 179Net::Radius::Server::DBStore - Store Radius packets into a Tied Hash 180 181=head1 SYNOPSIS 182 183 use MLDBM::Sync; 184 use MLDBM qw(DB_File Storable); 185 use Net::Radius::Server::DBStore; 186 use Net::Radius::Server::Base qw/:set/; 187 188 my $obj = Net::Radius::Server::DBStore->new 189 ({ 190 log_level => 4, 191 param => [ 'MLDBM::Sync', 192 @Tie_Opts ], 193 store => [qw/packet peer_addr port/], 194 pre_store_hook => sub { ... }, 195 sync => 1, 196 single => 1, 197 internal_tie => 1, 198 frozen => 0, 199 key_attrs => [ 'Acct-Session-Id', [ Vendor => 'PrivateSession' ] ], 200 hashref => \%external_hash, 201 result => NRS_SET_CONTINUE, 202 }); 203 204 my $sub = $obj->mk(); 205 206 # or 207 208 my $sub = Net::Radius::Server::DBStore->mk 209 ({ 210 # ... same parameters as above ... 211 }); 212 213=head1 DESCRIPTION 214 215C<Net::Radius::Server::DBStore> is a match or set method factory than 216can be used within C<Net::Radius::Server::Rule> objects. 217 218Note that this factory can produce either match or set methods. The 219only practical difference is the actual result to be returned, that 220defaults to C<NRS_SET_CONTINUE>. This is so, as it is anticipated that 221the most common use for this class would be producing set methods, so 222that accounting packets can be stored after classification that can be 223made using corresponding match methods. 224 225You can trivially replace the result to be returning by using the 226C<result> key, as shown in the SYNOPSIS. 227 228=over 229 230=item C<-E<gt>new($hashref)> 231 232Creates a new Net::Radius::Server::DBStore(3) object that acts as aod 233factory. C<$hashref> referenes a hash with the attributes that will 234apply to this object, so that multiple methods (that will share the 235same underlying object) can be created and given to different rules. 236 237=item C<-E<gt>mk($hashref)> 238 239Invokes C<-E<gt>new()> passing the given C<$hashref> if needed. 240 241At this stage, an object-private hash is tied to the specified class 242(MLDBM::Sync(3) as in the SYNOPSIS), using the given flags. This 243hash is stored in the object and will be shared by any methods 244constructed from it. 245 246This makes more efficient the case where you want to store information 247coming from various different rules, such as when matching for 248different types of service, more efficient. 249 250C<-E<gt>mk()> then returns a method that is suitable to be employed as 251either a match or set method within a C<Net::Radius::Server::Rule> 252object. 253 254=item C<$self-E<gt>mk()> or C<__PACKAGE__-E<gt>mk($hashref)> 255 256This method returns a sub suitable for calling as either a match or 257set method for a C<Net::Radius::Server::Rule> object. The resulting 258sub will return C<NRS_SET_CONTINUE> by default, unless overriden by 259the given configuration. 260 261The sub contains a closure where the object attributes -- Actually, 262the object itself -- are kept. 263 264When invoked as an object method (ie, C<$self-E<gt>mk()>), no 265arguments can be given. The object is preserved as is within the 266closure. 267 268When invoked as a class method (ie, C<__PACKAGE__-E<gt>mk($hashref)>), 269a new object is created with the given arguments and then, this object 270is preserved within the closure. This form is useful for compact 271filter definitions that require little or no surrounding code or 272holding variables. 273 274=item C<-E<gt>_do_tie()> 275 276You're not supposed to call this method directly. It is called by the 277sub produced with C<-E<gt>mk()>. Within this method, the following 278takes place: 279 280=over 281 282=item * 283 284The record key is calculated by using the corresponding configuration 285entry. 286 287=item * 288 289The requested information is stored in the tied hash, thus inserted in 290the underlying storage method. 291 292=item * 293 294The required return value is passed back to the caller. 295 296=back 297 298=back 299 300=head2 Configuration Keys 301 302The following configuration keys are understood by this class, in 303addition to the ones handled by Net::Radius::Server::Base(3). Note 304that those are available in the factory object (the one retured by the 305call to C<-E<gt>new()>) as same-name accessors. 306 307=over 308 309=item B<param =E<gt> [ @args ]> 310 311The actual parameters to the C<tie>. This parameter is mandatory. The 312first item in the C<@args> list has to be the name of the class to 313tie. Tipically you will want to use MLDBM(3), MLDBM::Sync(3), 314BerkeleyDB::Hash(3) or Tie::DBI(3). 315 316 param => [ 'MLDBM::Sync', '/my/db/file.db' ], 317 318Note that concurrency will be an issue. You need to insure that you 319use modules and settings that consider the fact that multiple 320instances will be writing at the same time. 321 322=item B<key_attrs =E<gt> [ @keys ]> 323 324Specify the Radius attributes to use as the record key for accessing 325the database. Each element of the list can be one of the following types: 326 327=over 328 329=item B<Scalar> 330 331This is either an attribute name or a delimiter. Actually, any string 332is used to look up the corresponding attribute in the request 333packet. If this fails, the actual string is inserted as the value of 334the key. Upon success, the value of the corresponding attribute is 335inserted in the key. 336 337=item B<sub or CODE ref> 338 339This sub will be called with the following arguments: The 340Net::Radius::Server::DBStore(3) object, the C<tied()> object as 341returned by C<tie>, a reference to the tied hash, a reference to the 342hash with data passed to the method and a Net::Radius::Packet(3) 343object with the decoded request this rule is responding to. 344 345The return value of the sub will be inserted in the key. 346 347This is useful to create hash keys that depend on information not 348within the actual Radius request. 349 350=item B<ArrayRef> 351 352This is interpreted as a VSA. The first element of the given list 353encodes the vendor name. The second attribute encodes the vendor 354attribute name. 355 356If the attribute is found within the request packet, its value is 357substituted at the current location of the key. Otherwise, an empty 358string will be substituted in its place. 359 360=back 361 362The following example: 363 364 key_attrs => [ 'Acct-Session-Id', '|', [ Cisco => 'Foo' ] ] 365 366Would produce a key like this: 367 368 DEADBEEF872374628742| 369 370Or if the ficticious VSA was defined, something like 371 372 DEADBEEF872374628742|The_Value 373 374The default attribute list is C<[ 'NAS-IP-Address', '|', 375'Acct-Session-Id' ]> which is likely to be suitable for Radius 376accounting packets. Note that RFC-2866 states that the 377C<Acct-Session-Id> attribute is unique, but this is generally so 378within a single device. When multiple devices are served, there may be 379a chance of collision. Including the IP Address of the NAS helps solve 380the problem. You must review your own environment and insure that the 381given key will produce unique values for each session. 382 383=item B<store =E<gt> [ @items ]> 384 385Tells the method which pieces of information to store within the tied 386hash. This corresponds to the attributes that are passed to the actual 387method. You might want to take a look at Net::Radius::Server::NS(3) 388and Net::Radius::Server::Rule(3) for more information. 389 390You should be conservative with this config entry, to store only as 391much information as needed. Note that you might be storing potentially 392sensitive information, such as user passwords, so appropiate care 393should be taken. 394 395The dafault value for C<@items> is C<packet, peer_addr, peer_host, 396peer_port, port>. This default should avoid storing huge objects 397alongside the useful data. 398 399Be aware that storing decoded packets (ie, including either C<request> 400or C<response> on the list of C<@items>) will lead to storing the NAS 401shared secret and the dictionaries using to encode and decode the 402packets. This will be large. 403 404=item B<pre_store_hook =E<gt> $sub> 405 406This C<$sub> will be called before actually calculating and storing in 407the BerkeleyDB(3) database. The following arguments are passed, in 408this order: The Net::Radius::Server::DBStore(3) object, the 409C<tied()> object as returned, a reference to the tied 410hash, a reference to the hash with data passed to the method, a 411Net::Radius::Packet(3) object with the decoded request this rule is 412responding to and the calculated key for this entry. 413 414The return value of the sub is currently ignored. 415 416=item B<sync =E<gt> $value> 417 418Causes a call to C<-E<gt>db_sync()> after each insertion when 419C<$value> evaluates to true, which is the default. When set to a false 420value, no calls will be made. 421 422The call to C<-E<gt>db_sync()> probably causes a performance hit. 423 424=item B<single =E<gt> $value> 425 426When set to true (the default), stores all the required elements as a 427single hash. When set to false, each tuple is stored individually 428within a hashref associated to the key. 429 430=item B<frozen =E<gt> $value> 431 432When set to true (the default), uses C<freeze()> from Storable(3) to 433serialize the values prior to storing. 434 435=item B<internal_tie =E<gt> $value> 436 437When true, the default, C<tie()> will be performed on the hash. In 438certain cases, you might want to "share" a hash. In these cases, the 439actual tying can be done elsewhere. 440 441=item B<hashref =E<gt> $hashref> 442 443Tells the factory to work with an external hash. This is useful to 444have external code modifying the underlying hash outside of a RADIUS 445transaction. 446 447If not provided, each call to C<-E<gt>mk()> ties a private hash. Note 448that you can use C<hashref> in a call to C<-E<gt>new()>, and then all 449the functions generated with C<-E<gt>mk()> will share the same hash. 450 451=back 452 453=head2 EXPORT 454 455None by default. 456 457=head1 BUGS 458 459This code uses C<die()> currently, however it is likely that 460C<croak()> would be better. The problem with this, is that using 461C<croak()> as intended, results in Perl returning errors like this 462one... 463 464 Bizarre copy of HASH in sassign 465 at /usr/share/perl/5.10/Carp/Heavy.pm line 96. 466 467while running C<make test> in my test machine. Since I don't want to 468run any risks, I'll stick to the C<die()> calls which do not 469manipulate the stack so much. 470 471=head1 SEE ALSO 472 473Perl(1), BerkeleyDB(3), Class::Accessor(3), MLDBM(3), MLDBM::Sync(3), 474Net::Radius::Packet(3), Net::Radius::Server(3), 475Net::Radius::Server::Base(3), Net::Radius::Server::NS(3), 476Net::Radius::Server::Rule(3), Storable(3), Tie::DBI(3). 477 478=head1 AUTHOR 479 480Luis E. Muñoz, E<lt>luismunoz@cpan.orgE<gt> 481 482=head1 COPYRIGHT AND LICENSE 483 484Copyright (C) 2006-2009 by Luis E. Muñoz 485 486This library is free software; you can redistribute it and/or modify 487it under the terms of the GPL version 2. 488 489=cut 490 491 492