diff options
Diffstat (limited to 'FS/FS/part_export/sqlradius.pm')
-rw-r--r-- | FS/FS/part_export/sqlradius.pm | 552 |
1 files changed, 0 insertions, 552 deletions
diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm deleted file mode 100644 index 10bccb0..0000000 --- a/FS/FS/part_export/sqlradius.pm +++ /dev/null @@ -1,552 +0,0 @@ -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::part_export; -use FS::svc_acct; -use FS::export_svc; - -@ISA = qw(FS::part_export); - -$DEBUG = 0; - -tie %options, 'Tie::IxHash', - 'datasrc' => { label=>'DBI data source ' }, - 'username' => { label=>'Database username' }, - 'password' => { label=>'Database password' }, - 'ignore_accounting' => { - type => 'checkbox', - label => 'Ignore accounting records from this database' - }, - 'hide_ip' => { - type => 'checkbox', - label => 'Hide IP address information on session reports', - }, - 'hide_data' => { - type => 'checkbox', - label => 'Hide download/upload information on session reports', - }, - 'show_called_station' => { - type => 'checkbox', - label => 'Show the Called-Station-ID on session reports', - }, -; - -$notes1 = <<'END'; -Real-time export of <b>radcheck</b>, <b>radreply</b> and <b>usergroup</b> -tables to any SQL database for -<a href="http://www.freeradius.org/">FreeRADIUS</a> -or <a href="http://radius.innercite.com/">ICRADIUS</a>. -END - -$notes2 = <<'END'; -An existing RADIUS database will be updated in realtime, but you can use -<a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a> -to delete the entire RADIUS database and repopulate the tables from the -Freeside database. See the -<a href="http://search.cpan.org/dist/DBI/DBI.pm#connect">DBI documentation</a> -and the -<a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a> -for the exact syntax of a DBI data source. -<ul> - <li>Using FreeRADIUS 0.9.0 with the PostgreSQL backend, the db_postgresql.sql schema and postgresql.conf queries contain incompatible changes. This is fixed in 0.9.1. Only new installs with 0.9.0 and PostgreSQL are affected - upgrades and other database backends and versions are unaffected. - <li>Using ICRADIUS, add a dummy "op" column to your database: - <blockquote><code> - ALTER TABLE radcheck ADD COLUMN op VARCHAR(2) NOT NULL DEFAULT '=='<br> - ALTER TABLE radreply ADD COLUMN op VARCHAR(2) NOT NULL DEFAULT '=='<br> - ALTER TABLE radgroupcheck ADD COLUMN op VARCHAR(2) NOT NULL DEFAULT '=='<br> - ALTER TABLE radgroupreply ADD COLUMN op VARCHAR(2) NOT NULL DEFAULT '==' - </code></blockquote> - <li>Using Radiator, see the - <a href="http://www.open.com.au/radiator/faq.html#38">Radiator FAQ</a> - for configuration information. -</ul> -END - -%info = ( - 'svc' => 'svc_acct', - 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS)', - 'options' => \%options, - 'nodomain' => 'Y', - 'notes' => $notes1. - 'This export does not export RADIUS realms (see also '. - 'sqlradius_withdomain). '. - $notes2 -); - -sub rebless { shift; } - -sub export_username { - my($self, $svc_acct) = (shift, shift); - warn "export_username called on $self with arg $svc_acct" if $DEBUG; - $svc_acct->username; -} - -sub _export_insert { - my($self, $svc_acct) = (shift, shift); - - foreach my $table (qw(reply check)) { - my $method = "radius_$table"; - my %attrib = $svc_acct->$method(); - next unless keys %attrib; - my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert', - $table, $self->export_username($svc_acct), %attrib ); - return $err_or_queue unless ref($err_or_queue); - } - my @groups = $svc_acct->radius_groups; - if ( @groups ) { - my $err_or_queue = $self->sqlradius_queue( - $svc_acct->svcnum, 'usergroup_insert', - $self->export_username($svc_acct), @groups ); - return $err_or_queue unless ref($err_or_queue); - } - ''; -} - -sub _export_replace { - my( $self, $new, $old ) = (shift, 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 $jobnum = ''; - if ( $self->export_username($old) ne $self->export_username($new) ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename', - $self->export_username($new), $self->export_username($old) ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; - } - $jobnum = $err_or_queue->jobnum; - } - - foreach my $table (qw(reply check)) { - my $method = "radius_$table"; - my %new = $new->$method(); - my %old = $old->$method(); - if ( grep { !exists $old{$_} #new attributes - || $new{$_} ne $old{$_} #changed - } keys %new - ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert', - $table, $self->export_username($new), %new ); - 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 @del = grep { !exists $new{$_} } keys %old; - if ( @del ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'attrib_delete', - $table, $self->export_username($new), @del ); - 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; - } - } - } - } - - # (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; - } - - 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; - } - } - } - - 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; - } - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - - ''; -} - -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) ); - ref($err_or_queue) ? '' : $err_or_queue; -} - -sub sqlradius_queue { - my( $self, $svcnum, $method ) = (shift, shift, shift); - my $queue = new FS::queue { - 'svcnum' => $svcnum, - 'job' => "FS::part_export::sqlradius::sqlradius_$method", - }; - $queue->insert( - $self->option('datasrc'), - $self->option('username'), - $self->option('password'), - @_, - ) or $queue; -} - -sub sqlradius_insert { #subroutine, not method - my $dbh = sqlradius_connect(shift, shift, shift); - my( $table, $username, %attributes ) = @_; - - foreach my $attribute ( keys %attributes ) { - - my $s_sth = $dbh->prepare( - "SELECT COUNT(*) FROM rad$table WHERE UserName = ? AND Attribute = ?" - ) or die $dbh->errstr; - $s_sth->execute( $username, $attribute ) or die $s_sth->errstr; - - if ( $s_sth->fetchrow_arrayref->[0] ) { - - my $u_sth = $dbh->prepare( - "UPDATE rad$table SET Value = ? WHERE UserName = ? AND Attribute = ?" - ) or die $dbh->errstr; - $u_sth->execute($attributes{$attribute}, $username, $attribute) - or die $u_sth->errstr; - - } else { - - my $i_sth = $dbh->prepare( - "INSERT INTO rad$table ( UserName, Attribute, op, Value ) ". - "VALUES ( ?, ?, ?, ? )" - ) or die $dbh->errstr; - $i_sth->execute( - $username, - $attribute, - ( $attribute =~ /Password/i ? '==' : ':=' ), - $attributes{$attribute}, - ) or die $i_sth->errstr; - - } - - } - $dbh->disconnect; -} - -sub sqlradius_usergroup_insert { #subroutine, not method - my $dbh = sqlradius_connect(shift, shift, shift); - my( $username, @groups ) = @_; - - my $sth = $dbh->prepare( - "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )" - ) or die $dbh->errstr; - foreach my $group ( @groups ) { - $sth->execute( $username, $group ) - or die "can't insert into groupname table: ". $sth->errstr; - } - $dbh->disconnect; -} - -sub sqlradius_usergroup_delete { #subroutine, not method - my $dbh = sqlradius_connect(shift, shift, shift); - my( $username, @groups ) = @_; - - my $sth = $dbh->prepare( - "DELETE FROM usergroup WHERE UserName = ? AND GroupName = ?" - ) or die $dbh->errstr; - foreach my $group ( @groups ) { - $sth->execute( $username, $group ) - or die "can't delete from groupname table: ". $sth->errstr; - } - $dbh->disconnect; -} - -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 $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?") - or die $dbh->errstr; - $sth->execute($new_username, $old_username) - or die "can't update $table: ". $sth->errstr; - } - $dbh->disconnect; -} - -sub sqlradius_attrib_delete { #subroutine, not method - my $dbh = sqlradius_connect(shift, shift, shift); - my( $table, $username, @attrib ) = @_; - - foreach my $attribute ( @attrib ) { - my $sth = $dbh->prepare( - "DELETE FROM rad$table WHERE UserName = ? AND Attribute = ?" ) - or die $dbh->errstr; - $sth->execute($username,$attribute) - or die "can't delete from rad$table table: ". $sth->errstr; - } - $dbh->disconnect; -} - -sub sqlradius_delete { #subroutine, not method - my $dbh = sqlradius_connect(shift, shift, shift); - my $username = shift; - - 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; - } - $dbh->disconnect; -} - -sub sqlradius_connect { - #my($datasrc, $username, $password) = @_; - #DBI->connect($datasrc, $username, $password) or die $DBI::errstr; - DBI->connect(@_) or die $DBI::errstr; -} - -#-- - -=item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ] - -TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see -L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion -functions. - -SVC_ACCT, if specified, limits the results to the specified account. - -IP, if specified, limits the results to the specified IP address. - -PREFIX, if specified, limits the results to records with a matching -Called-Station-ID. - -#SQL_SELECT defaults to * if unspecified. It can be useful to set it to -#SUM(acctsessiontime) or SUM(AcctInputOctets), etc. - -Returns an arrayref of hashrefs with the following fields: - -=over 4 - -=item username - -=item framedipaddress - -=item acctstarttime - -=item acctstoptime - -=item acctsessiontime - -=item acctinputoctets - -=item acctoutputoctets - -=item calledstationid - -=back - -=cut - -#some false laziness w/cust_svc::seconds_since_sqlradacct - -sub usage_sessions { - my( $self, $start, $end ) = splice(@_, 0, 3); - my $svc_acct = @_ ? shift : ''; - my $ip = @_ ? shift : ''; - my $prefix = @_ ? shift : ''; - #my $select = @_ ? shift : '*'; - - $end ||= 2147483647; - - return [] if $self->option('ignore_accounting'); - - my $dbh = sqlradius_connect( map $self->option($_), - qw( datasrc username password ) ); - - #select a unix time conversion function based on database type - my $str2time; - if ( $dbh->{Driver}->{Name} =~ /^mysql(PP)?$/ ) { - $str2time = 'UNIX_TIMESTAMP('; - } elsif ( $dbh->{Driver}->{Name} eq 'Pg' ) { - $str2time = 'EXTRACT( EPOCH FROM '; - } else { - warn "warning: unknown database type ". $dbh->{Driver}->{Name}. - "; guessing how to convert to UNIX timestamps"; - $str2time = 'extract(epoch from '; - } - - my @fields = ( - qw( username realm framedipaddress - acctsessiontime acctinputoctets acctoutputoctets - calledstationid - ), - "$str2time acctstarttime ) as acctstarttime", - "$str2time acctstoptime ) as acctstoptime", - ); - - my @param = (); - my $where = ''; - - if ( $svc_acct ) { - my $username = $self->export_username($svc_acct); - if ( $svc_acct =~ /^([^@]+)\@([^@]+)$/ ) { - $where = '( UserName = ? OR ( UserName = ? AND Realm = ? ) ) AND'; - push @param, $username, $1, $2; - } else { - $where = 'UserName = ? AND'; - push @param, $username; - } - } - - if ( length($ip) ) { - $where .= ' FramedIPAddress = ? AND'; - push @param, $ip; - } - - if ( length($prefix) ) { - #assume sip: for now, else things get ugly trying to match /^\w+:$prefix/ - $where .= " CalledStationID LIKE 'sip:$prefix\%' AND"; - } - - push @param, $start, $end; - - my $sth = $dbh->prepare('SELECT '. join(', ', @fields). - " FROM radacct - WHERE $where - $str2time AcctStopTime ) >= ? - AND $str2time AcctStopTime ) <= ? - ORDER BY AcctStartTime DESC - ") or die $dbh->errstr; - $sth->execute(@param) or die $sth->errstr; - - [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ]; - -} - -=item update_svc_acct - -=cut - -sub update_svc_acct { - my $self = shift; - - my $dbh = sqlradius_connect( map $self->option($_), - qw( datasrc username password ) ); - - my @fields = qw( radacctid username realm acctsessiontime ); - - my @param = (); - my $where = ''; - - my $sth = $dbh->prepare(" - SELECT RadAcctId, UserName, Realm, AcctSessionTime - FROM radacct - WHERE FreesideStatus IS NULL - AND AcctStopTime != 0 - ") or die $dbh->errstr; - $sth->execute() or die $sth->errstr; - - while ( my $row = $sth->fetchrow_arrayref ) { - my($RadAcctId, $UserName, $Realm, $AcctSessionTime) = @$row; - warn "processing record: ". - "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s" - if $DEBUG; - - 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 = - grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum, - 'svcpart' => $_->cust_svc->svcpart, } ) - } - qsearch( 'svc_acct', - { 'username' => $UserName }, - '', - $extra_sql - ); - - 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; - 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 "setting FreesideStatus to $status $errinfo\n" if $DEBUG; - my $psth = $dbh->prepare("UPDATE radacct - SET FreesideStatus = ? - WHERE RadAcctId = ?" - ) or die $dbh->errstr; - $psth->execute($status, $RadAcctId) or die $psth->errstr; - - } - -} - -1; - |