RADIUS exports that skip provisioning, for accounting purposes, RT#73739
[freeside.git] / FS / FS / part_export / sqlradius.pm
index 1f5b018..9e65e51 100644 (file)
@@ -10,6 +10,7 @@ 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 );
@@ -25,6 +26,10 @@ tie %options, 'Tie::IxHash',
                    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'
@@ -79,10 +84,6 @@ tie %options, 'Tie::IxHash',
   'disconnect_port' => {
     label => 'Port to send disconnection requests to, default 1700',
   },
-  'disconnect_log' => {
-    label => 'Print disconnect output and errors to the queue log (will otherwise fail silently)',
-    type => 'checkbox',
-  },
 ;
 
 $notes1 = <<'END';
@@ -157,6 +158,8 @@ sub radius_check { #override for other svcdb
 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);
@@ -182,6 +185,8 @@ sub _export_insert {
 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';
@@ -194,22 +199,6 @@ sub _export_replace {
   my $dbh = dbh;
 
   my $jobnum = '';
-
-  # disconnect users before changing username
-  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'),
-      'disconnect_log'    => $self->option('disconnect_log'),
-    );
-    unless ( ref($err_or_queue) ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $err_or_queue;
-    }
-    $jobnum = $err_or_queue->jobnum; # chain all of these dependencies
-  }
-
   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',
@@ -218,13 +207,6 @@ sub _export_replace {
       $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;
-      }
-    }
     $jobnum = $err_or_queue->jobnum;
   }
 
@@ -274,7 +256,7 @@ sub _export_replace {
   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,
@@ -285,6 +267,27 @@ sub _export_replace {
     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;
 
   '';
@@ -294,6 +297,8 @@ sub _export_replace {
 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';
@@ -309,21 +314,6 @@ sub _export_suspend {
 
   my $jobnum = '';
 
-  # disconnect users before changing anything
-  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'),
-      'disconnect_log'    => $self->option('disconnect_log'),
-    );
-    unless ( ref($err_or_queue) ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $err_or_queue;
-    }
-    $jobnum = $err_or_queue->jobnum;
-  }
-
   my @newgroups = $self->suspended_usergroups($svc_acct);
 
   unless (@newgroups) { #don't change password if assigning to a suspended group
@@ -334,16 +324,11 @@ sub _export_suspend {
       $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;
-      }
-    }
+    $jobnum = $err_or_queue->jobnum;
   }
 
-  my $error =
+  my $error;
+  ($error,$jobnum) =
     $self->sqlreplace_usergroups(
       $new->svcnum,
       $self->export_username($new),
@@ -355,6 +340,28 @@ sub _export_suspend {
     $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;
 
   '';
@@ -363,6 +370,8 @@ sub _export_suspend {
 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';
@@ -402,26 +411,28 @@ sub _export_unsuspend {
 sub _export_delete {
   my( $self, $svc_x ) = (shift, shift);
 
+  return '' if $self->option('skip_provisioning');
+
   my $jobnum = '';
 
-  # disconnect users before changing anything
+  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'),
-      'disconnect_log'    => $self->option('disconnect_log'),
     );
     return $err_or_queue unless ref($err_or_queue);
-    $jobnum = $err_or_queue->jobnum;
-  }
-
-  my $usergroup = $self->option('usergroup') || 'usergroup';
-  my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'delete',
-    $self->export_username($svc_x), $usergroup );
-  if ( $jobnum ) {
-    my $error = $err_or_queue->depend_insert( $jobnum );
-    return $error if $error;
+    if ( $jobnum ) {
+      my $error = $err_or_queue->depend_insert( $jobnum );
+      return $error if $error;
+    }
   }
 
   ref($err_or_queue) ? '' : $err_or_queue;
@@ -616,6 +627,8 @@ sub sqlradius_connect {
   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) = @_;
 
@@ -657,8 +670,9 @@ sub sqlreplace_usergroups {
       my $error = $err_or_queue->depend_insert( $jobnum );
       return $error if $error;
     }
+    $jobnum = $err_or_queue->jobnum; # chain all of these dependencies
   }
-  '';
+  wantarray ? ('',$jobnum) : '';
 }
 
 
@@ -1252,8 +1266,6 @@ 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)
 
-I<disconnect_log> - if true, print disconnect command & output to the error log
-
 Note this is NOT the opposite of sqlradius_connect.
 
 =cut
@@ -1269,21 +1281,26 @@ sub sqlradius_user_disconnect {
   $dbh->disconnect();
   die "No nas found in radius db" unless @$nas;
   # set up ssh connection
-  eval "use Net::SSH";
   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);
-    warn $command . "\n" . $output . $errput . $ssh->error . "\n"
-      if $opt{'disconnect_log'};
+    $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 '';
 }