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::Record qw( dbh qsearch qsearchs str2time_sql str2time_sql_closing );
use FS::part_export;
use FS::svc_acct;
use FS::export_svc;
use Carp qw( cluck );
use NEXT;
+use Net::OpenSSH;
@ISA = qw(FS::part_export);
@EXPORT_OK = qw( sqlradius_connect );
type => 'select',
options => [qw( usergroup radusergroup ) ],
},
+ 'skip_provisioning' => {
+ type => 'checkbox',
+ label => 'Skip provisioning records to this database'
+ },
'ignore_accounting' => {
type => 'checkbox',
label => 'Ignore accounting records from this database'
},
'show_called_station' => {
type => 'checkbox',
- label => 'Show the Called-Station-ID on session reports',
+ label => 'Show the Called-Station-ID on session reports', #as a phone number
},
'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 => 'checkbox',
label => 'Export RADIUS group attributes to this database',
},
+ 'disconnect_ssh' => {
+ label => 'To send a disconnection request to each RADIUS client when modifying, suspending or deleting an account, enter a ssh connection string (username@host) with access to the radclient program',
+ },
+ 'disconnect_port' => {
+ label => 'Port to send disconnection requests to, default 1700',
+ },
;
$notes1 = <<'END';
sub _export_insert {
my($self, $svc_x) = (shift, shift);
+ return '' if $self->option('skip_provisioning');
+
foreach my $table (qw(reply check)) {
my $method = "radius_$table";
my %attrib = $self->$method($svc_x);
sub _export_replace {
my( $self, $new, $old ) = (shift, shift, shift);
+ return '' if $self->option('skip_provisioning');
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
my $error;
my (@oldgroups) = $old->radius_groups('hashref');
my (@newgroups) = $new->radius_groups('hashref');
- $error = $self->sqlreplace_usergroups( $new->svcnum,
+ ($error,$jobnum) = $self->sqlreplace_usergroups( $new->svcnum,
$self->export_username($new),
$jobnum ? $jobnum : '',
\@oldgroups,
return $error;
}
+ # radius database is used for authorization, so to avoid users reauthorizing
+ # before the database changes, disconnect users after changing database
+ if ($self->option('disconnect_ssh')) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'user_disconnect',
+ 'disconnect_ssh' => $self->option('disconnect_ssh'),
+ 'svc_acct_username' => $old->username,
+ 'disconnect_port' => $self->option('disconnect_port'),
+ );
+ 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);
+ return '' if $self->option('skip_provisioning');
+
my $new = $svc_acct->clone_suspended;
local $SIG{HUP} = 'IGNORE';
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ my $jobnum = '';
+
my @newgroups = $self->suspended_usergroups($svc_acct);
unless (@newgroups) { #don't change password if assigning to a suspended group
$dbh->rollback if $oldAutoCommit;
return $err_or_queue;
}
-
+ $jobnum = $err_or_queue->jobnum;
}
- my $error =
+ my $error;
+ ($error,$jobnum) =
$self->sqlreplace_usergroups(
$new->svcnum,
$self->export_username($new),
$dbh->rollback if $oldAutoCommit;
return $error;
}
+
+ # radius database is used for authorization, so to avoid users reauthorizing
+ # before the database changes, disconnect users after changing database
+ if ($self->option('disconnect_ssh')) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'user_disconnect',
+ 'disconnect_ssh' => $self->option('disconnect_ssh'),
+ 'svc_acct_username' => $svc_acct->username,
+ 'disconnect_port' => $self->option('disconnect_port'),
+ );
+ 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_unsuspend {
my( $self, $svc_x ) = (shift, shift);
+ return '' if $self->option('skip_provisioning');
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
sub _export_delete {
my( $self, $svc_x ) = (shift, shift);
+
+ return '' if $self->option('skip_provisioning');
+
+ my $jobnum = '';
+
my $usergroup = $self->option('usergroup') || 'usergroup';
my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'delete',
$self->export_username($svc_x), $usergroup );
+ $jobnum = $err_or_queue->jobnum;
+
+ # radius database is used for authorization, so to avoid users reauthorizing
+ # before the database changes, disconnect users after changing database
+ if ($self->option('disconnect_ssh')) {
+ my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'user_disconnect',
+ 'disconnect_ssh' => $self->option('disconnect_ssh'),
+ 'svc_acct_username' => $svc_x->username,
+ 'disconnect_port' => $self->option('disconnect_port'),
+ );
+ return $err_or_queue unless ref($err_or_queue);
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ return $error if $error;
+ }
+ }
+
ref($err_or_queue) ? '' : $err_or_queue;
}
DBI->connect(@_) or die $DBI::errstr;
}
+# on success, returns '' in scalar context, ('',$jobnum) in list context
+# on error, always just returns error
sub sqlreplace_usergroups {
my ($self, $svcnum, $username, $jobnum, $old, $new) = @_;
my $error = $err_or_queue->depend_insert( $jobnum );
return $error if $error;
}
+ $jobnum = $err_or_queue->jobnum; # chain all of these dependencies
}
- '';
+ wantarray ? ('',$jobnum) : '';
}
=item acctoutputoctets
+=item callingstationid
+
=item calledstationid
=back
$opt = shift;
$start = $opt->{stoptime_start};
$end = $opt->{stoptime_end};
- $svc_acct = $opt->{svc_acct};
+ $svc_acct = $opt->{svc} || $opt->{svc_acct};
$ip = $opt->{ip};
$prefix = $opt->{prefix};
$summarize = $opt->{summarize};
qw( datasrc username password ) );
#select a unix time conversion function based on database type
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $closing = str2time_sql_closing( $dbh->{Driver}->{Name} );
my @fields = (
qw( username realm framedipaddress
acctsessiontime acctinputoctets acctoutputoctets
- calledstationid
+ callingstationid calledstationid
),
- "$str2time acctstarttime ) as acctstarttime",
- "$str2time acctstoptime ) as acctstoptime",
+ "$str2time acctstarttime $closing as acctstarttime",
+ "$str2time acctstoptime $closing as acctstoptime",
);
@fields = ( 'username', 'sum(acctsessiontime) as acctsessiontime', 'sum(acctinputoctets) as acctinputoctets',
my $acctstoptime = '';
if ( $opt->{session_status} ne 'open' ) {
if ( $start ) {
- $acctstoptime .= "$str2time AcctStopTime ) >= ?";
+ $acctstoptime .= "$str2time AcctStopTime $closing >= ?";
push @param, $start;
$acctstoptime .= ' AND ' if $end;
}
if ( $end ) {
- $acctstoptime .= "$str2time AcctStopTime ) <= ?";
+ $acctstoptime .= "$str2time AcctStopTime $closing <= ?";
push @param, $end;
}
}
push @where, $acctstoptime;
if ( $opt->{starttime_start} ) {
- push @where, "$str2time AcctStartTime ) >= ?";
+ push @where, "$str2time AcctStartTime $closing >= ?";
push @param, $opt->{starttime_start};
}
if ( $opt->{starttime_end} ) {
- push @where, "$str2time AcctStartTime ) <= ?";
+ push @where, "$str2time AcctStartTime $closing <= ?";
push @param, $opt->{starttime_end};
}
my $dbh = sqlradius_connect( map $self->option($_),
qw( datasrc username password ) );
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $closing = str2time_sql_closing( $dbh->{Driver}->{Name} );
+
my @fields = qw( radacctid username realm acctsessiontime );
my @param = ();
my $sth = $dbh->prepare("
SELECT RadAcctId, UserName, Realm, AcctSessionTime,
- $str2time AcctStartTime), $str2time AcctStopTime),
+ $str2time AcctStartTime $closing, $str2time AcctStopTime $closing,
AcctInputOctets, AcctOutputOctets
FROM radacct
WHERE FreesideStatus IS NULL
$fs_username = lc($fs_username) unless $conf->exists('username-uppercase');
- #my %search = ( 'username' => $UserName );
+ #my %search = ( 'username' => $fs_username );
my $status = '';
my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ".
or die $dbh->errstr;
}
+=item sqlradius_user_disconnect
+
+For a specified user, sends a disconnect request to all nas in the server database.
+
+Accepts L</sqlradius_connect> connection input and the following named parameters:
+
+I<disconnect_ssh> - user@host with access to radclient program (required)
+
+I<svc_acct_username> - the user to be disconnected (required)
+
+I<disconnect_port> - the port (on the nas) to send disconnect requests to (defaults to 1700)
+
+Note this is NOT the opposite of sqlradius_connect.
+
+=cut
+
+sub sqlradius_user_disconnect {
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my %opt = @_;
+ # get list of nas
+ my $sth = $dbh->prepare('select nasname, secret from nas') or die $dbh->errstr;
+ $sth->execute() or die $dbh->errstr;
+ my $nas = $sth->fetchall_arrayref({});
+ $sth->finish();
+ $dbh->disconnect();
+ die "No nas found in radius db" unless @$nas;
+ # set up ssh connection
+ my $ssh = Net::OpenSSH->new($opt{'disconnect_ssh'});
+ die "Couldn't establish SSH connection: " . $ssh->error
+ if $ssh->error;
+ # send individual disconnect requests
+ my $user = $opt{'svc_acct_username'}; #svc_acct username
+ my $port = $opt{'disconnect_port'} || 1700; #or should we pull this from the db?
+ my $error = '';
+ foreach my $nas (@$nas) {
+ my $nasname = $nas->{'nasname'};
+ my $secret = $nas->{'secret'};
+ my $command = qq(echo "User-Name=$user" | radclient -r 1 $nasname:$port disconnect '$secret');
+ my ($output, $errput) = $ssh->capture2($command);
+ $error .= "Error running $command: $errput " . $ssh->error . " "
+ if $errput || $ssh->error;
+ }
+ $error .= "Some clients may have successfully disconnected"
+ if $error && (@$nas > 1);
+ $error = "No clients found"
+ unless @$nas;
+ die $error if $error;
+ return '';
+}
+
###
# class method to fetch groups/attributes from the sqlradius install on upgrade
###