package FS::part_export::textradius; use vars qw(@ISA %info $prefix); use Fcntl qw(:flock); use Tie::IxHash; use FS::UID qw(datasrc); use FS::part_export; @ISA = qw(FS::part_export); tie my %options, 'Tie::IxHash', 'user' => { label=>'Remote username', default=>'root' }, 'users' => { label=>'users file location', default=>'/etc/raddb/users' }, ; %info = ( 'svc' => 'svc_acct', 'desc' => 'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)', 'options' => \%options, 'default_svc_class' => 'Internet', 'notes' => <<'END' This will edit a text RADIUS users file in place on a remote server. Requires installation of RADIUS::UserFile from CPAN. If using RADIUS::UserFile 1.01, make sure to apply this patch. Also make sure rsync is installed on the remote machine, and SSH is setup for unattended operation. END ); $prefix = "%%%FREESIDE_CONF%%%/export."; sub rebless { shift; } sub _export_insert { my($self, $svc_acct) = (shift, shift); $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'insert', $svc_acct->username, $svc_acct->radius_check, '-', $svc_acct->radius_reply); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_replace { my( $self, $new, $old ) = (shift, shift, shift); return "can't (yet?) change username with textradius" if $old->username ne $new->username; #return '' unless $old->_password ne $new->_password; $err_or_queue = $self->textradius_queue( $new->svcnum, 'insert', $new->username, $new->radius_check, '-', $new->radius_reply); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_delete { my( $self, $svc_acct ) = (shift, shift); $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'delete', $svc_acct->username ); ref($err_or_queue) ? '' : $err_or_queue; } #a good idea to queue anything that could fail or take any time sub textradius_queue { my( $self, $svcnum, $method ) = (shift, shift, shift); my $queue = new FS::queue { 'svcnum' => $svcnum, 'job' => "FS::part_export::textradius::textradius_$method", }; $queue->insert( $self->option('user')||'root', $self->machine, $self->option('users'), @_, ) or $queue; } sub textradius_insert { #subroutine, not method my( $user, $host, $users, $username, @attributes ) = @_; #silly arg processing my($att, @check); push @check, $att while @attributes && ($att=shift @attributes) ne '-'; my %check = @check; my %reply = @attributes; my $file = textradius_download($user, $host, $users); eval "use RADIUS::UserFile;"; die $@ if $@; my $userfile = new RADIUS::UserFile( File => $file, Who => [ $username ], Check_Items => [ keys %check ], ) or die "error parsing $file"; $userfile->remove($username); $userfile->add( Who => $username, Attributes => { %check, %reply }, Comment => 'user added by Freeside', ) or die "error adding to $file"; $userfile->update( Who => [ $username ] ) or die "error updating $file"; textradius_upload($user, $host, $users); } sub textradius_delete { #subroutine, not method my( $user, $host, $users, $username ) = @_; my $file = textradius_download($user, $host, $users); eval "use RADIUS::UserFile;"; die $@ if $@; my $userfile = new RADIUS::UserFile( File => $file, Who => [ $username ], ) or die "error parsing $file"; $userfile->remove($username); $userfile->update( Who => [ $username ] ) or die "error updating $file"; textradius_upload($user, $host, $users); } sub textradius_download { my( $user, $host, $users ) = @_; my $dir = $prefix. datasrc; mkdir $dir, 0700 or die $! unless -d $dir; $dir .= "/$host"; mkdir $dir, 0700 or die $! unless -d $dir; my $dest = "$dir/users"; eval "use File::Rsync;"; die $@ if $@; my $rsync = File::Rsync->new({ rsh => 'ssh' }); open(LOCK, "+>>$dest.lock") and flock(LOCK,LOCK_EX) or die "can't open $dest.lock: $!"; $rsync->exec( { src => "$user\@$host:$users", dest => $dest, } ); # true/false return value from exec is not working, alas if ( $rsync->err ) { die "error downloading $user\@$host:$users : ". 'exit status: '. $rsync->status. ', '. 'STDERR: '. join(" / ", $rsync->err). ', '. 'STDOUT: '. join(" / ", $rsync->out); } $dest; } sub textradius_upload { my( $user, $host, $users ) = @_; my $dir = $prefix. datasrc. "/$host"; eval "use File::Rsync;"; die $@ if $@; my $rsync = File::Rsync->new({ rsh => 'ssh', #dry_run => 1, }); $rsync->exec( { src => "$dir/users", dest => "$user\@$host:$users", } ); # true/false return value from exec is not working, alas if ( $rsync->err ) { die "error uploading to $user\@$host:$users : ". 'exit status: '. $rsync->status. ', '. 'STDERR: '. join(" / ", $rsync->err). ', '. 'STDOUT: '. join(" / ", $rsync->out); } flock(LOCK,LOCK_UN); close LOCK; } 1;