X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fsqlradius.pm;h=c51429de21373087fd82acdfe2a51d1ca116282e;hb=5d1a52f6d855d1bd2dbb414cefef06f1da7e94b0;hp=a89d4d764d7964f1958b2cc5529c8ee63c2a04ee;hpb=5f3a89734813a7a2da10e517de92e9c92a22830c;p=freeside.git diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index a89d4d764..c51429de2 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -1,6 +1,7 @@ package FS::part_export::sqlradius; -use vars qw(@ISA $DEBUG %info %options $notes1 $notes2); +use vars qw(@ISA @EXPORT_OK $DEBUG %info %options $notes1 $notes2); +use Exporter; use Tie::IxHash; use FS::Record qw( dbh qsearch qsearchs str2time_sql ); use FS::part_export; @@ -9,17 +10,32 @@ use FS::export_svc; use Carp qw( cluck ); @ISA = qw(FS::part_export); +@EXPORT_OK = qw( sqlradius_connect ); $DEBUG = 0; +my %groups; tie %options, 'Tie::IxHash', 'datasrc' => { label=>'DBI data source ' }, 'username' => { label=>'Database username' }, 'password' => { label=>'Database password' }, + 'usergroup' => { label => 'Group table', + type => 'select', + options => [qw( usergroup radusergroup ) ], + }, 'ignore_accounting' => { type => 'checkbox', label => 'Ignore accounting records from this database' }, + 'process_single_realm' => { + type => 'checkbox', + label => 'Only process one realm of accounting records', + }, + 'realm' => { label => 'The realm of of accounting records to be processed' }, + 'ignore_long_sessions' => { + type => 'checkbox', + label => 'Ignore sessions which span billing periods', + }, 'hide_ip' => { type => 'checkbox', label => 'Hide IP address information on session reports', @@ -32,7 +48,21 @@ tie %options, 'Tie::IxHash', type => 'checkbox', label => 'Show the Called-Station-ID on session reports', }, - 'overlimit_groups' => { label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit', } , + 'overlimit_groups' => { + label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit (if not overridden by overlimit_groups global or per-agent config)', + type => 'select', + multi => 1, + option_label => sub { + $groups{$_[0]}; + }, + option_values => sub { + %groups = ( + map { $_->groupnum, $_->long_description } + qsearch('radius_group', {}), + ); + sort keys (%groups); + }, + } , 'groups_susp_reason' => { label => 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)', type => 'textarea', @@ -41,7 +71,7 @@ tie %options, 'Tie::IxHash', ; $notes1 = <<'END'; -Real-time export of radcheck, radreply and usergroup +Real-time export of radcheck, radreply and usergroup/radusergroup tables to any SQL database for FreeRADIUS or ICRADIUS. @@ -49,7 +79,7 @@ END $notes2 = <<'END'; An existing RADIUS database will be updated in realtime, but you can use -freeside-sqlradius-reset +freeside-sqlradius-reset to delete the entire RADIUS database and repopulate the tables from the Freeside database. See the DBI documentation @@ -95,24 +125,25 @@ sub export_username { } sub _export_insert { - my($self, $svc_acct) = (shift, shift); + my($self, $svc_x) = (shift, shift); foreach my $table (qw(reply check)) { my $method = "radius_$table"; - my %attrib = $svc_acct->$method(); + my %attrib = $svc_x->$method(); next unless keys %attrib; - my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert', - $table, $self->export_username($svc_acct), %attrib ); + my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'insert', + $table, $self->export_username($svc_x), %attrib ); return $err_or_queue unless ref($err_or_queue); } - my @groups = $svc_acct->radius_groups; + my @groups = $svc_x->radius_groups; if ( @groups ) { - cluck localtime(). ": queuing usergroup_insert for ". $svc_acct->svcnum. - " (". $self->export_username($svc_acct). " with ". join(", ", @groups) + cluck localtime(). ": queuing usergroup_insert for ". $svc_x->svcnum. + " (". $self->export_username($svc_x). " with ". join(", ", @groups) if $DEBUG; + my $usergroup = $self->option('usergroup') || 'usergroup'; my $err_or_queue = $self->sqlradius_queue( - $svc_acct->svcnum, 'usergroup_insert', - $self->export_username($svc_acct), @groups ); + $svc_x->svcnum, 'usergroup_insert', + $self->export_username($svc_x), $usergroup, @groups ); return $err_or_queue unless ref($err_or_queue); } ''; @@ -134,8 +165,9 @@ sub _export_replace { my $jobnum = ''; if ( $self->export_username($old) ne $self->export_username($new) ) { + my $usergroup = $self->option('usergroup') || 'usergroup'; my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename', - $self->export_username($new), $self->export_username($old) ); + $self->export_username($new), $self->export_username($old), $usergroup ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -219,20 +251,24 @@ sub _export_suspend { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert', - 'check', $self->export_username($new), $new->radius_check ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; + my @newgroups = $self->suspended_usergroups($svc_acct); + + unless (@newgroups) { #don't change password if assigning to a suspended group + + my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert', + 'check', $self->export_username($new), $new->radius_check ); + unless ( ref($err_or_queue) ) { + $dbh->rollback if $oldAutoCommit; + return $err_or_queue; + } + } - my $error; - my (@newgroups) = $self->suspended_usergroups($svc_acct); - $error = + my $error = $self->sqlreplace_usergroups( $new->svcnum, $self->export_username($new), '', - $svc_acct->usergroup, + [ $svc_acct->radius_groups ], \@newgroups, ); if ( $error ) { @@ -271,7 +307,7 @@ sub _export_unsuspend { $self->export_username($svc_acct), '', \@oldgroups, - $svc_acct->usergroup, + [ $svc_acct->radius_groups ], ); if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -283,9 +319,10 @@ sub _export_unsuspend { } sub _export_delete { - my( $self, $svc_acct ) = (shift, shift); - my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'delete', - $self->export_username($svc_acct) ); + my( $self, $svc_x ) = (shift, shift); + my $usergroup = $self->option('usergroup') || 'usergroup'; + my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'delete', + $self->export_username($svc_x), $usergroup ); ref($err_or_queue) ? '' : $err_or_queue; } @@ -321,7 +358,7 @@ sub suspended_usergroups { if (!$userspec && exists($reasonmap{$r->reason})); } my $suspend_user; - if ($userspec =~ /^d+$/ ){ + if ($userspec =~ /^\d+$/ ){ $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } ); }elsif ($userspec =~ /^\S+\@\S+$/){ my ($username,$domain) = split(/\@/, $userspec); @@ -376,14 +413,16 @@ sub sqlradius_insert { #subroutine, not method sub sqlradius_usergroup_insert { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); - my( $username, @groups ) = @_; + my $username = shift; + my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup'; + my @groups = @_; my $s_sth = $dbh->prepare( - "SELECT COUNT(*) FROM usergroup WHERE UserName = ? AND GroupName = ?" + "SELECT COUNT(*) FROM $usergroup WHERE UserName = ? AND GroupName = ?" ) or die $dbh->errstr; my $sth = $dbh->prepare( - "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )" + "INSERT INTO $usergroup ( UserName, GroupName ) VALUES ( ?, ? )" ) or die $dbh->errstr; foreach my $group ( @groups ) { @@ -397,15 +436,25 @@ sub sqlradius_usergroup_insert { #subroutine, not method $sth->execute( $username, $group ) or die "can't insert into groupname table: ". $sth->errstr; } + if ( $s_sth->{Active} ) { + warn "sqlradius s_sth still active; calling ->finish()"; + $s_sth->finish; + } + if ( $sth->{Active} ) { + warn "sqlradius sth still active; calling ->finish()"; + $sth->finish; + } $dbh->disconnect; } sub sqlradius_usergroup_delete { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); - my( $username, @groups ) = @_; + my $username = shift; + my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup'; + my @groups = @_; my $sth = $dbh->prepare( - "DELETE FROM usergroup WHERE UserName = ? AND GroupName = ?" + "DELETE FROM $usergroup WHERE UserName = ? AND GroupName = ?" ) or die $dbh->errstr; foreach my $group ( @groups ) { $sth->execute( $username, $group ) @@ -416,8 +465,9 @@ sub sqlradius_usergroup_delete { #subroutine, not method sub sqlradius_rename { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); - my($new_username, $old_username) = @_; - foreach my $table (qw(radreply radcheck usergroup )) { + my($new_username, $old_username) = (shift, shift); + my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup'; + foreach my $table (qw(radreply radcheck), $usergroup ) { my $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?") or die $dbh->errstr; $sth->execute($new_username, $old_username) @@ -443,8 +493,9 @@ sub sqlradius_attrib_delete { #subroutine, not method sub sqlradius_delete { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); my $username = shift; + my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup'; - foreach my $table (qw( radcheck radreply usergroup )) { + foreach my $table (qw( radcheck radreply), $usergroup ) { my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" ); $sth->execute($username) or die "can't delete from $table table: ". $sth->errstr; @@ -473,9 +524,11 @@ sub sqlreplace_usergroups { push @delgroups, $oldgroup; } + my $usergroup = $self->option('usergroup') || 'usergroup'; + if ( @delgroups ) { my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_delete', - $username, @delgroups ); + $username, $usergroup, @delgroups ); return $err_or_queue unless ref($err_or_queue); if ( $jobnum ) { @@ -489,7 +542,7 @@ sub sqlreplace_usergroups { "with ". join(", ", @newgroups) if $DEBUG; my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_insert', - $username, @newgroups ); + $username, $usergroup, @newgroups ); return $err_or_queue unless ref($err_or_queue); if ( $jobnum ) { @@ -576,13 +629,15 @@ sub usage_sessions { my $opt = {}; my($start, $end, $svc_acct, $ip, $prefix) = ( '', '', '', '', ''); + my $summarize = 0; if ( ref($_[0]) ) { - my $opt = shift; + $opt = shift; $start = $opt->{stoptime_start}; $end = $opt->{stoptime_end}; $svc_acct = $opt->{svc_acct}; $ip = $opt->{ip}; $prefix = $opt->{prefix}; + $summarize = $opt->{summarize}; } else { ( $start, $end ) = splice(@_, 0, 2); $svc_acct = @_ ? shift : ''; @@ -610,12 +665,16 @@ sub usage_sessions { "$str2time acctstoptime ) as acctstoptime", ); + @fields = ( 'username', 'sum(acctsessiontime) as acctsessiontime', 'sum(acctinputoctets) as acctinputoctets', + 'sum(acctoutputoctets) as acctoutputoctets', + ) if $summarize; + my @param = (); - my @where = ''; + my @where = (); if ( $svc_acct ) { my $username = $self->export_username($svc_acct); - if ( $svc_acct =~ /^([^@]+)\@([^@]+)$/ ) { + if ( $username =~ /^([^@]+)\@([^@]+)$/ ) { push @where, '( UserName = ? OR ( UserName = ? AND Realm = ? ) )'; push @param, $username, $1, $2; } else { @@ -624,6 +683,11 @@ sub usage_sessions { } } + if ($self->option('process_single_realm')) { + push @where, 'Realm = ?'; + push @param, $self->option('realm'); + } + if ( length($ip) ) { push @where, ' FramedIPAddress = ?'; push @param, $ip; @@ -657,11 +721,15 @@ sub usage_sessions { my $where = join(' AND ', @where); $where = "WHERE $where" if $where; + my $groupby = ''; + $groupby = 'GROUP BY username' if $summarize; + + my $orderby = 'ORDER BY AcctStartTime DESC'; + $orderby = '' if $summarize; + my $sth = $dbh->prepare('SELECT '. join(', ', @fields). - " FROM radacct - $where - ORDER BY AcctStartTime DESC - ") or die $dbh->errstr; + " FROM radacct $where $groupby $orderby + ") or die $dbh->errstr; $sth->execute(@param) or die $sth->errstr; [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ]; @@ -672,7 +740,7 @@ sub usage_sessions { =cut -sub update_svc_acct { +sub update_svc { my $self = shift; my $conf = new FS::Conf; @@ -706,7 +774,7 @@ sub update_svc_acct { $UserName = lc($UserName) unless $conf->exists('username-uppercase'); - my %search = ( 'username' => $UserName ); + #my %search = ( 'username' => $UserName ); my $extra_sql = ''; if ( ref($self) =~ /withdomain/ ) { #well... @@ -717,43 +785,53 @@ sub update_svc_acct { my $oldAutoCommit = $FS::UID::AutoCommit; # can't undo side effects, but at local $FS::UID::AutoCommit = 0; # least we can avoid over counting - my @svc_acct = - grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum, - 'svcpart' => $_->cust_svc->svcpart, } ) - } - qsearch( 'svc_acct', - { 'username' => $UserName }, - '', - $extra_sql - ); - + my $status = 'skipped'; my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ". "(UserName $UserName, Realm $Realm)"; - my $status = 'skipped'; - if ( !@svc_acct ) { - warn "WARNING: no svc_acct record found $errinfo - skipping\n"; - } elsif ( scalar(@svc_acct) > 1 ) { - warn "WARNING: multiple svc_acct records found $errinfo - skipping\n"; - } else { - - my $svc_acct = $svc_acct[0]; - warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG; - $svc_acct->last_login($AcctStartTime); - $svc_acct->last_logout($AcctStopTime); - - my $cust_pkg = $svc_acct->cust_svc->cust_pkg; - if ( $cust_pkg && $AcctStopTime < ( $cust_pkg->last_bill - || $cust_pkg->setup ) ) { - $status = 'skipped (too old)'; + if ( $self->option('process_single_realm') + && $self->option('realm') ne $Realm ) + { + warn "WARNING: wrong realm $errinfo - skipping\n" if $DEBUG; + } else { + my @svc_acct = + grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum, + 'svcpart' => $_->cust_svc->svcpart, } ) + } + qsearch( 'svc_acct', + { 'username' => $UserName }, + '', + $extra_sql + ); + + if ( !@svc_acct ) { + warn "WARNING: no svc_acct record found $errinfo - skipping\n"; + } elsif ( scalar(@svc_acct) > 1 ) { + warn "WARNING: multiple svc_acct records found $errinfo - skipping\n"; } else { - my @st; - push @st, _try_decrement($svc_acct, 'seconds', $AcctSessionTime ); - push @st, _try_decrement($svc_acct, 'upbytes', $AcctInputOctets ); - push @st, _try_decrement($svc_acct, 'downbytes', $AcctOutputOctets ); - push @st, _try_decrement($svc_acct, 'totalbytes', $AcctInputOctets + + my $svc_acct = $svc_acct[0]; + warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG; + + $svc_acct->last_login($AcctStartTime); + $svc_acct->last_logout($AcctStopTime); + + my $session_time = $AcctStopTime; + $session_time = $AcctStartTime if $self->option('ignore_long_sessions'); + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + if ( $cust_pkg && $session_time < ( $cust_pkg->last_bill + || $cust_pkg->setup ) ) { + $status = 'skipped (too old)'; + } else { + my @st; + push @st, _try_decrement($svc_acct, 'seconds', $AcctSessionTime); + push @st, _try_decrement($svc_acct, 'upbytes', $AcctInputOctets); + push @st, _try_decrement($svc_acct, 'downbytes', $AcctOutputOctets); + push @st, _try_decrement($svc_acct, 'totalbytes', $AcctInputOctets + $AcctOutputOctets); - $status=join(' ', @st); + $status=join(' ', @st); + } } } @@ -799,7 +877,7 @@ sub all_sqlradius { my @part_export = (); push @part_export, qsearch('part_export', { 'exporttype' => $_ } ) - foreach qw(sqlradius sqlradius_withdomain radiator); + foreach qw( sqlradius sqlradius_withdomain radiator phone_sqlradius ); @part_export; }