fix agent-specific logos migrated from 1.7, RT#4645
[freeside.git] / FS / FS / svc_acct.pm
index f26b210..6f11051 100644 (file)
@@ -14,6 +14,7 @@ use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles
              $radius_password $radius_ip
              $dirhash
              @saltset @pw_set );
+use Scalar::Util qw( blessed );
 use Carp;
 use Fcntl qw(:flock);
 use Date::Format;
@@ -46,7 +47,7 @@ $DEBUG = 0;
 $me = '[FS::svc_acct]';
 
 #ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::svc_acct'} = sub { 
+FS::UID->install_callback( sub { 
   $conf = new FS::Conf;
   $dir_prefix = $conf->config('home');
   @shells = $conf->config('shells');
@@ -85,7 +86,8 @@ $FS::UID::callback{'FS::svc_acct'} = sub {
   $radius_password = $conf->config('radius-password') || 'Password';
   $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
   @pw_set = ( 'A'..'Z' ) if $conf->exists('password-generated-allcaps');
-};
+}
+);
 
 @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
 @pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
@@ -248,7 +250,8 @@ sub table_info {
                         label    => 'Shell',
                          def_label=> 'Shell (set to blank for no shell tracking)',
                          type     =>'select',
-                         select_list => [ $conf->config('shells') ],
+                         #select_list => [ $conf->config('shells') ],
+                         select_list => [ $conf ? $conf->config('shells') : () ],
                          disable_inventory => 1,
                          disable_select => 1,
                        },
@@ -336,6 +339,8 @@ sub table_info {
 
 sub table { 'svc_acct'; }
 
+sub table_dupcheck_fields { ( 'username', 'domsvc' ); }
+
 sub _fieldhandlers {
   {
     #false laziness with edit/svc_acct.cgi
@@ -368,15 +373,6 @@ sub _lastlog {
          ' ('. $self->email. "): $time\n"
       if $DEBUG;
 
-    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 $sql = "UPDATE svc_acct SET last_log$op = ? WHERE svcnum = ?";
@@ -391,9 +387,6 @@ sub _lastlog {
     die "Can't update last_log$op for svcnum". $self->svcnum
       if $rv == 0;
 
-    warn "$me update successful; committing\n"
-      if $DEBUG;
-    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
     $self->{'Hash'}->{"last_log$op"} = $time;
   }else{
     $self->getfield("last_log$op");
@@ -510,12 +503,6 @@ sub insert {
     $self->svcpart($cust_svc->svcpart);
   }
 
-  $error = $self->_check_duplicate;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $error;
-  }
-
   my @jobnums;
   $error = $self->SUPER::insert(
     'jobnums'       => \@jobnums,
@@ -746,14 +733,15 @@ contain an arrayref of group names.  See L<FS::radius_usergroup>.
 =cut
 
 sub replace {
-  my ( $new, $old ) = ( shift, shift );
-  my $error;
+  my $new = shift;
+
+  my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+              ? shift
+              : $new->replace_old;
+
   warn "$me replacing $old with $new\n" if $DEBUG;
 
-  # We absolutely have to have an old vs. new record to make this work.
-  if (!defined($old)) {
-    $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } );
-  }
+  my $error;
 
   return "can't modify system account" if $old->_check_system;
 
@@ -827,15 +815,6 @@ sub replace {
 
   }
 
-  if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
-    $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
-    $error = $new->_check_duplicate;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
   $error = $new->SUPER::replace($old, @_);
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
@@ -1144,13 +1123,13 @@ sub check {
 
     if ( $recref->{_password} =~
            #/^(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/
-           /^(!!?)?(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/
+           /^(!!?)?(\$\w+\$.*|[\w\+\/\.]{13}|_[\w\+\/\.]{19}|\*)$/
        ) {
 
-      $recref->{_password} = $1.$2;
+      $recref->{_password} = ( defined($1) ? $1 : '' ). $2;
 
     } else {
-      return 'Illegal (crypt-encoded) password';
+      return 'Illegal (crypt-encoded) password: '. $recref->{_password};
     }
 
   } elsif ( $recref->{_password_encoding} eq 'plain' ) { 
@@ -1237,7 +1216,7 @@ sub _check_system {
 
 =item _check_duplicate
 
-Internal function to check for duplicates usernames, username@domain pairs and
+Internal method to check for duplicates usernames, username@domain pairs and
 uids.
 
 If the I<global_unique-username> configuration value is set to B<username> or
@@ -1254,20 +1233,7 @@ sub _check_duplicate {
   my $global_unique = $conf->config('global_unique-username') || 'none';
   return '' if $global_unique eq 'disabled';
 
-  warn "$me locking svc_acct table for duplicate search" if $DEBUG;
-  if ( driver_name =~ /^Pg/i ) {
-    dbh->do("LOCK TABLE svc_acct IN SHARE ROW EXCLUSIVE MODE")
-      or die dbh->errstr;
-  } elsif ( driver_name =~ /^mysql/i ) {
-    dbh->do("SELECT * FROM duplicate_lock
-               WHERE lockname = 'svc_acct'
-              FOR UPDATE"
-          ) or die dbh->errstr;
-  } else {
-    die "unknown database ". driver_name.
-        "; don't know how to lock for duplicate search";
-  }
-  warn "$me acquired svc_acct table lock for duplicate search" if $DEBUG;
+  $self->lock_table;
 
   my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } );
   unless ( $part_svc ) {
@@ -1331,7 +1297,8 @@ sub _check_duplicate {
     foreach my $dup_user ( @dup_user ) {
       my $dup_svcpart = $dup_user->cust_svc->svcpart;
       if ( exists($conflict_user_svcpart{$dup_svcpart}) ) {
-        return "duplicate username: conflicts with svcnum ". $dup_user->svcnum.
+        return "duplicate username ". $self->username.
+               ": conflicts with svcnum ". $dup_user->svcnum.
                " via exportnum ". $conflict_user_svcpart{$dup_svcpart};
       }
     }
@@ -1339,9 +1306,9 @@ sub _check_duplicate {
     foreach my $dup_userdomain ( @dup_userdomain ) {
       my $dup_svcpart = $dup_userdomain->cust_svc->svcpart;
       if ( exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
-        return "duplicate username\@domain: conflicts with svcnum ".
-               $dup_userdomain->svcnum. " via exportnum ".
-               $conflict_userdomain_svcpart{$dup_svcpart};
+        return "duplicate username\@domain ". $self->email.
+               ": conflicts with svcnum ". $dup_userdomain->svcnum.
+               " via exportnum ". $conflict_userdomain_svcpart{$dup_svcpart};
       }
     }
 
@@ -1349,9 +1316,11 @@ sub _check_duplicate {
       my $dup_svcpart = $dup_uid->cust_svc->svcpart;
       if ( exists($conflict_user_svcpart{$dup_svcpart})
            || exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
-        return "duplicate uid: conflicts with svcnum ". $dup_uid->svcnum.
-               " via exportnum ". $conflict_user_svcpart{$dup_svcpart}
-                                 || $conflict_userdomain_svcpart{$dup_svcpart};
+        return "duplicate uid ". $self->uid.
+               ": conflicts with svcnum ". $dup_uid->svcnum.
+               " via exportnum ".
+               ( $conflict_user_svcpart{$dup_svcpart}
+                 || $conflict_userdomain_svcpart{$dup_svcpart} );
       }
     }
 
@@ -1433,8 +1402,9 @@ sub radius_check {
       ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
     } grep { /^rc_/ && $self->getfield($_) } fields( $self->table );
 
-  my $password = $self->_password;
-  my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password';  $check{$pw_attrib} = $password;
+
+  my($pw_attrib, $password) = $self->radius_password;
+  $check{$pw_attrib} = $password;
 
   my $cust_svc = $self->cust_svc;
   die "FATAL: no cust_svc record for svc_acct.svcnum ". $self->svcnum. "\n"
@@ -1448,6 +1418,43 @@ sub radius_check {
 
 }
 
+=item radius_password 
+
+Returns a key/value pair containing the RADIUS attribute name and value
+for the password.
+
+=cut
+
+sub radius_password {
+  my $self = shift;
+
+  my($pw_attrib, $password);
+  if ( $self->_password_encoding eq 'ldap' ) {
+
+    $pw_attrib = 'Password-With-Header';
+    $password = $self->_password;
+
+  } elsif ( $self->_password_encoding eq 'crypt' ) {
+
+    $pw_attrib = 'Crypt-Password';
+    $password = $self->_password;
+
+  } elsif ( $self->_password_encoding eq 'plain' ) {
+
+    $pw_attrib = $radius_password; #Cleartext-Password?  man rlm_pap
+    $password = $self->_password;
+
+  } else {
+
+    $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password';
+    $password = $self->_password;
+
+  }
+
+  ($pw_attrib, $password);
+
+}
+
 =item snapshot
 
 This method instructs the object to "snapshot" or freeze RADIUS check and