RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / sqlradius.pm
index 1f5b018..764db2e 100644 (file)
@@ -8,8 +8,10 @@ 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 Carp qw( carp cluck );
 use NEXT;
+use Net::OpenSSH;
+use FS::DBI;
 
 @ISA = qw(FS::part_export);
 @EXPORT_OK = qw( sqlradius_connect );
@@ -25,6 +27,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 +85,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 +159,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 +186,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 +200,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 +208,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 +257,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 +268,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 +298,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 +315,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 +325,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 +341,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 +371,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 +412,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;
@@ -478,6 +490,12 @@ sub suspended_usergroups {
 }
 
 sub sqlradius_insert { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_insert() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my( $table, $username, %attributes ) = @_;
 
@@ -516,6 +534,12 @@ sub sqlradius_insert { #subroutine, not method
 }
 
 sub sqlradius_usergroup_insert { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_usergroup_insert() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my $username = shift;
   my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup';
@@ -554,6 +578,12 @@ sub sqlradius_usergroup_insert { #subroutine, not method
 }
 
 sub sqlradius_usergroup_delete { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_usergroup_delete() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my $username = shift;
   my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup';
@@ -571,6 +601,12 @@ sub sqlradius_usergroup_delete { #subroutine, not method
 }
 
 sub sqlradius_rename { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_rename() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my($new_username, $old_username) = (shift, shift);
   my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup';
@@ -584,6 +620,12 @@ sub sqlradius_rename { #subroutine, not method
 }
 
 sub sqlradius_attrib_delete { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_attrib_delete() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my( $table, $username, @attrib ) = @_;
 
@@ -598,6 +640,12 @@ sub sqlradius_attrib_delete { #subroutine, not method
 }
 
 sub sqlradius_delete { #subroutine, not method
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_delete() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my $username = shift;
   my $usergroup = ( $_[0] =~ /^(rad)?usergroup/i ) ? shift : 'usergroup';
@@ -613,9 +661,11 @@ sub sqlradius_delete { #subroutine, not method
 sub sqlradius_connect {
   #my($datasrc, $username, $password) = @_;
   #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
-  DBI->connect(@_) or die $DBI::errstr;
+  FS::DBI->connect(@_) or die $FS::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 +707,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) : '';
 }
 
 
@@ -869,6 +920,12 @@ sub usage_sessions {
 sub update_svc {
   my $self = shift;
 
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'update_svc() suppressed by noexport_hack'
+      if $self->option('debug') || $DEBUG;
+    return;
+  }
+
   my $conf = new FS::Conf;
 
   my $fdbh = dbh;
@@ -1034,6 +1091,13 @@ sub export_nas_replace { shift->export_nas_action('replace', @_); }
 sub export_nas_action {
   my $self = shift;
   my ($action, $new, $old) = @_;
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_nas_action($action) suppressed by noexport_hack"
+      if $self->option('debug') || $DEBUG;
+    return;
+  }
+
   # find the NAS in the target table by its name
   my $nasname = ($action eq 'replace') ? $old->nasname : $new->nasname;
   my $nasnum = $new->nasnum;
@@ -1047,6 +1111,12 @@ sub export_nas_action {
 }
 
 sub sqlradius_nas_insert {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_nas_insert() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
   my $nas = qsearchs('nas', { nasnum => $opt{'nasnum'} })
@@ -1061,6 +1131,12 @@ VALUES (?, ?, ?, ?, ?, ?, ?)');
 }
 
 sub sqlradius_nas_delete {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_nas_delete() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
   my $sth = $dbh->prepare('DELETE FROM nas WHERE nasname = ?');
@@ -1068,6 +1144,12 @@ sub sqlradius_nas_delete {
 }
 
 sub sqlradius_nas_replace {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_nas_replace() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
   my $nas = qsearchs('nas', { nasnum => $opt{'nasnum'} })
@@ -1143,6 +1225,12 @@ sub export_attr_action {
 }
 
 sub sqlradius_attr_insert {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_attr_insert() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
 
@@ -1166,6 +1254,12 @@ sub sqlradius_attr_insert {
 }
 
 sub sqlradius_attr_delete {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_attr_delete() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
 
@@ -1217,6 +1311,12 @@ sub export_group_replace {
 }
 
 sub sqlradius_group_replace {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_group_replace() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my $usergroup = shift;
   $usergroup =~ /^(rad)?usergroup$/
@@ -1252,13 +1352,17 @@ 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
 
 sub sqlradius_user_disconnect {
+
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp 'sqlradius_user_disconnect() suppressed by noexport_hack' if $DEBUG;
+    return;
+  }
+
   my $dbh = sqlradius_connect(shift, shift, shift);
   my %opt = @_;
   # get list of nas
@@ -1269,21 +1373,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 '';
 }
 
@@ -1306,10 +1415,10 @@ sub _upgrade_exporttype {
 
 sub import_attrs {
   my $self = shift;
-  my $dbh =  DBI->connect( map $self->option($_),
+  my $dbh =  FS::DBI->connect( map $self->option($_),
                                    qw( datasrc username password ) );
   unless ( $dbh ) {
-    warn "Error connecting to RADIUS server: $DBI::errstr\n";
+    warn "Error connecting to RADIUS server: $FS::DBI::errstr\n";
     return;
   }