X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fsqlradius.pm;h=9995952c50016ac7aa14b109fec78d5cbe961d55;hb=7dda9d81f13c733358d4b68d538434350696e969;hp=10bccb0347b3b221ec279335057538a716925874;hpb=c8cccb4a92adceb943c635fe62dad0d034462ce0;p=freeside.git diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 10bccb034..9995952c5 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -2,10 +2,11 @@ package FS::part_export::sqlradius; use vars qw(@ISA $DEBUG %info %options $notes1 $notes2); use Tie::IxHash; -use FS::Record qw( dbh qsearch ); +use FS::Record qw( dbh qsearch qsearchs ); use FS::part_export; use FS::svc_acct; use FS::export_svc; +use Carp qw( cluck ); @ISA = qw(FS::part_export); @@ -31,6 +32,12 @@ 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', } , + 'groups_susp_reason' => { label => + 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)', + type => 'textarea', + }, + ; $notes1 = <<'END'; @@ -75,11 +82,15 @@ END $notes2 ); +sub _groups_susp_reason_map { map { reverse( /^\s*(\S+)\s*(.*)$/ ) } + split( "\n", shift->option('groups_susp_reason')); +} + sub rebless { shift; } sub export_username { my($self, $svc_acct) = (shift, shift); - warn "export_username called on $self with arg $svc_acct" if $DEBUG; + warn "export_username called on $self with arg $svc_acct" if $DEBUG > 1; $svc_acct->username; } @@ -96,6 +107,9 @@ sub _export_insert { } my @groups = $svc_acct->radius_groups; if ( @groups ) { + cluck localtime(). ": queuing usergroup_insert for ". $svc_acct->svcnum. + " (". $self->export_username($svc_acct). " with ". join(", ", @groups) + if $DEBUG; my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'usergroup_insert', $self->export_username($svc_acct), @groups ); @@ -170,50 +184,99 @@ sub _export_replace { } } - # (sorta) false laziness with FS::svc_acct::replace - my @oldgroups = @{$old->usergroup}; #uuuh - my @newgroups = $new->radius_groups; - my @delgroups = (); - foreach my $oldgroup ( @oldgroups ) { - if ( grep { $oldgroup eq $_ } @newgroups ) { - @newgroups = grep { $oldgroup ne $_ } @newgroups; - next; - } - push @delgroups, $oldgroup; + my $error; + my (@oldgroups) = $old->radius_groups; + my (@newgroups) = $new->radius_groups; + $error = $self->sqlreplace_usergroups( $new->svcnum, + $self->export_username($new), + $jobnum ? $jobnum : '', + \@oldgroups, + \@newgroups, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - if ( @delgroups ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_delete', - $self->export_username($new), @delgroups ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; - } - if ( $jobnum ) { - my $error = $err_or_queue->depend_insert( $jobnum ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; +} + +sub _export_suspend { + my( $self, $svc_acct ) = (shift, shift); + + my $new = $svc_acct->clone_suspended; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + 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; } - if ( @newgroups ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_insert', - $self->export_username($new), @newgroups ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; - } - if ( $jobnum ) { - my $error = $err_or_queue->depend_insert( $jobnum ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } + my $error; + my (@newgroups) = $self->suspended_usergroups($svc_acct); + $error = + $self->sqlreplace_usergroups( $new->svcnum, + $self->export_username($new), + '', + $svc_acct->usergroup, + \@newgroups, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; +} + +sub _export_unsuspend { + my( $self, $svc_acct ) = (shift, shift); + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert', + 'check', $self->export_username($svc_acct), $svc_acct->radius_check ); + unless ( ref($err_or_queue) ) { + $dbh->rollback if $oldAutoCommit; + return $err_or_queue; } + my $error; + my (@oldgroups) = $self->suspended_usergroups($svc_acct); + $error = $self->sqlreplace_usergroups( $svc_acct->svcnum, + $self->export_username($svc_acct), + '', + \@oldgroups, + $svc_acct->usergroup, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -240,6 +303,39 @@ sub sqlradius_queue { ) or $queue; } +sub suspended_usergroups { + my ($self, $svc_acct) = (shift, shift); + + return () unless $svc_acct; + + #false laziness with FS::part_export::shellcommands + #subclass part_export? + + my $r = $svc_acct->cust_svc->cust_pkg->last_reason; + my %reasonmap = $self->_groups_susp_reason_map; + my $userspec = ''; + if ($r) { + $userspec = $reasonmap{$r->reasonnum} + if exists($reasonmap{$r->reasonnum}); + $userspec = $reasonmap{$r->reason} + if (!$userspec && exists($reasonmap{$r->reason})); + } + my $suspend_user; + if ($userspec =~ /^d+$/ ){ + $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } ); + }elsif ($userspec =~ /^\S+\@\S+$/){ + my ($username,$domain) = split(/\@/, $userspec); + for my $user (qsearch( 'svc_acct', { 'username' => $username } )){ + $suspend_user = $user if $userspec eq $user->email; + } + }elsif ($userspec){ + $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } ); + } + #esalf + return $suspend_user->radius_groups if $suspend_user; + (); +} + sub sqlradius_insert { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); my( $table, $username, %attributes ) = @_; @@ -282,10 +378,22 @@ sub sqlradius_usergroup_insert { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); my( $username, @groups ) = @_; + my $s_sth = $dbh->prepare( + "SELECT COUNT(*) FROM usergroup WHERE UserName = ? AND GroupName = ?" + ) or die $dbh->errstr; + my $sth = $dbh->prepare( "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )" ) or die $dbh->errstr; + foreach my $group ( @groups ) { + $s_sth->execute( $username, $group ) or die $s_sth->errstr; + if ($s_sth->fetchrow_arrayref->[0]) { + warn localtime() . ": sqlradius_usergroup_insert attempted to reinsert " . + "$group for $username\n" + if $DEBUG; + next; + } $sth->execute( $username, $group ) or die "can't insert into groupname table: ". $sth->errstr; } @@ -350,6 +458,49 @@ sub sqlradius_connect { DBI->connect(@_) or die $DBI::errstr; } +sub sqlreplace_usergroups { + my ($self, $svcnum, $username, $jobnum, $old, $new) = @_; + + # (sorta) false laziness with FS::svc_acct::replace + my @oldgroups = @$old; + my @newgroups = @$new; + my @delgroups = (); + foreach my $oldgroup ( @oldgroups ) { + if ( grep { $oldgroup eq $_ } @newgroups ) { + @newgroups = grep { $oldgroup ne $_ } @newgroups; + next; + } + push @delgroups, $oldgroup; + } + + if ( @delgroups ) { + my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_delete', + $username, @delgroups ); + return $err_or_queue + unless ref($err_or_queue); + if ( $jobnum ) { + my $error = $err_or_queue->depend_insert( $jobnum ); + return $error if $error; + } + } + + if ( @newgroups ) { + cluck localtime(). ": queuing usergroup_insert for $svcnum ($username) ". + "with ". join(", ", @newgroups) + if $DEBUG; + my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_insert', + $username, @newgroups ); + return $err_or_queue + unless ref($err_or_queue); + if ( $jobnum ) { + my $error = $err_or_queue->depend_insert( $jobnum ); + return $error if $error; + } + } + ''; +} + + #-- =item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ] @@ -475,6 +626,8 @@ sub usage_sessions { sub update_svc_acct { my $self = shift; + my $conf = new FS::Conf; + my $dbh = sqlradius_connect( map $self->option($_), qw( datasrc username password ) ); @@ -484,7 +637,8 @@ sub update_svc_acct { my $where = ''; my $sth = $dbh->prepare(" - SELECT RadAcctId, UserName, Realm, AcctSessionTime + SELECT RadAcctId, UserName, Realm, AcctSessionTime, + AcctInputOctets, AcctOutputOctets FROM radacct WHERE FreesideStatus IS NULL AND AcctStopTime != 0 @@ -492,17 +646,20 @@ sub update_svc_acct { $sth->execute() or die $sth->errstr; while ( my $row = $sth->fetchrow_arrayref ) { - my($RadAcctId, $UserName, $Realm, $AcctSessionTime) = @$row; + my($RadAcctId, $UserName, $Realm, $AcctSessionTime, + $AcctInputOctets, $AcctOutputOctets) = @$row; warn "processing record: ". "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s" if $DEBUG; + $UserName = lc($UserName) unless $conf->exists('username-uppercase'); + my %search = ( 'username' => $UserName ); + my $extra_sql = ''; if ( ref($self) =~ /withdomain/ ) { #well... $extra_sql = " AND '$Realm' = ( SELECT domain FROM svc_domain WHERE svc_domain.svcnum = svc_acct.domsvc ) "; - my $svc_domain = qsearch } my @svc_acct = @@ -523,18 +680,16 @@ sub update_svc_acct { } 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; - if ( $svc_acct->seconds !~ /^$/ ) { - warn " svc_acct.seconds found (". $svc_acct->seconds. - ") - decrementing\n" - if $DEBUG; - my $error = $svc_acct->decrement_seconds($AcctSessionTime); - die $error if $error; - $status = 'done'; - } else { - warn " no existing seconds value for svc_acct - skiping\n" if $DEBUG; - } + warn "found svc_acct ". $svc_acct[0]->svcnum. " $errinfo\n" if $DEBUG; + _try_decrement($svc_acct[0], 'seconds', $AcctSessionTime) + and $status='done'; + _try_decrement($svc_acct[0], 'upbytes', $AcctInputOctets) + and $status='done'; + _try_decrement($svc_acct[0], 'downbytes', $AcctOutputOctets) + and $status='done'; + _try_decrement($svc_acct[0], 'totalbytes', $AcctInputOctets + + $AcctOutputOctets) + and $status='done'; } warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG; @@ -548,5 +703,21 @@ sub update_svc_acct { } +sub _try_decrement { + my ($svc_acct, $column, $amount) = @_; + if ( $svc_acct->$column !~ /^$/ ) { + warn " svc_acct.$column found (". $svc_acct->$column. + ") - decrementing\n" + if $DEBUG; + my $method = 'decrement_' . $column; + my $error = $svc_acct->$method($amount); + die $error if $error; + return 'done'; + } else { + warn " no existing $column value for svc_acct - skipping\n" if $DEBUG; + } + return ''; +} + 1;