package FS::part_export::shellcommands; use vars qw(@ISA %info); use Tie::IxHash; use String::ShellQuote; use FS::part_export; @ISA = qw(FS::part_export); tie my %options, 'Tie::IxHash', 'user' => { label=>'Remote username', default=>'root' }, 'useradd' => { label=>'Insert command', default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username' #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir' }, 'useradd_stdin' => { label=>'Insert command STDIN', type =>'textarea', default=>'', }, 'userdel' => { label=>'Delete command', default=>'userdel -r $username', #default=>'rm -rf $dir', }, 'userdel_stdin' => { label=>'Delete command STDIN', type =>'textarea', default=>'', }, 'usermod' => { label=>'Modify command', default=>'usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -p $new_crypt_password $old_username', #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '. # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '. # 'find . -depth -print | cpio -pdm $new_dir; '. # 'chmod u-t $new_dir; chown -R $uid.$gid $new_dir; '. # 'rm -rf $old_dir'. #')' }, 'usermod_stdin' => { label=>'Modify command STDIN', type =>'textarea', default=>'', }, 'usermod_pwonly' => { label=>'Disallow username, domain, uid, gid, and dir changes', #and RADIUS group changes', type =>'checkbox', }, 'usermod_nousername' => { label=>'Disallow just username changes', type =>'checkbox', }, 'suspend' => { label=>'Suspension command', default=>'usermod -L $username', }, 'suspend_stdin' => { label=>'Suspension command STDIN', default=>'', }, 'unsuspend' => { label=>'Unsuspension command', default=>'usermod -U $username', }, 'unsuspend_stdin' => { label=>'Unsuspension command STDIN', default=>'', }, 'crypt' => { label => 'Default password encryption', type=>'select', options=>[qw(crypt md5)], default => 'crypt', }, ; %info = ( 'svc' => 'svc_acct', 'desc' => 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)', 'options' => \%options, 'nodomain' => 'Y', 'notes' => <<'END' Run remote commands via SSH. Usernames are considered unique (also see shellcommands_withdomain). You probably want this if the commands you are running will not accept a domain as a parameter. You will need to setup SSH for unattended operation.

Use these buttons for some useful presets: The following variables are available for interpolation (prefixed with new_ or old_ for replace operations): END ); sub rebless { shift; } sub _export_insert { my($self) = shift; $self->_export_command('useradd', @_); } sub _export_delete { my($self) = shift; $self->_export_command('userdel', @_); } sub _export_suspend { my($self) = shift; $self->_export_command_or_super('suspend', @_); } sub _export_unsuspend { my($self) = shift; $self->_export_command_or_super('unsuspend', @_); } sub _export_command_or_super { my($self, $action) = (shift, shift); if ( $self->option($action) =~ /^\s*$/ ) { my $method = "SUPER::_export_$action"; $self->$method(@_); } else { $self->_export_command($action, @_); } }; sub _export_command { my ( $self, $action, $svc_acct) = (shift, shift, shift); my $command = $self->option($action); return '' if $command =~ /^\s*$/; my $stdin = $self->option($action."_stdin"); no strict 'vars'; { no strict 'refs'; ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; my $count = 1; foreach my $acct_snarf ( $svc_acct->acct_snarf ) { ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) ) foreach qw( machine username _password ); $count++; } } my $cust_pkg = $svc_acct->cust_svc->cust_pkg; if ( $cust_pkg ) { $email = ( grep { $_ !~ /^(POST|FAX)$/ } $cust_pkg->cust_main->invoicing_list )[0]; } else { $email = ''; } $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/; ($first, $last ) = ( $1, $2 ); $first = shell_quote $first; $last = shell_quote $last; $finger = shell_quote $finger; $quoted_password = shell_quote $_password; $domain = $svc_acct->domain; $crypt_password = shell_quote( $svc_acct->crypt_password( $self->option('crypt') ) ); @radius_groups = $svc_acct->radius_groups; $self->shellcommands_queue( $svc_acct->svcnum, user => $self->option('user')||'root', host => $self->machine, command => eval(qq("$command")), stdin_string => eval(qq("$stdin")), ); } sub _export_replace { my($self, $new, $old ) = (shift, shift, shift); my $command = $self->option('usermod'); my $stdin = $self->option('usermod_stdin'); no strict 'vars'; { no strict 'refs'; ${"old_$_"} = $old->getfield($_) foreach $old->fields; ${"new_$_"} = $new->getfield($_) foreach $new->fields; } $new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/; ($new_first, $new_last ) = ( $1, $2 ); $new_first = shell_quote $new_first; $new_last = shell_quote $new_last; $new_finger = shell_quote $new_finger; $quoted_new__password = shell_quote $new__password; #old, wrong? $new_quoted_password = shell_quote $new__password; #new, better? $old_domain = $old->domain; $new_domain = $new->domain; $new_crypt_password = shell_quote( $new->crypt_password( $self->option('crypt') ) ); @old_radius_groups = $old->radius_groups; @new_radius_groups = $new->radius_groups; my $error = ''; if ( $self->option('usermod_pwonly') || $self->option('usermod_nousername') ){ if ( $old_username ne $new_username ) { $error ||= "can't change username"; } } if ( $self->option('usermod_pwonly') ) { if ( $old_domain ne $new_domain ) { $error ||= "can't change domain"; } if ( $old_uid != $new_uid ) { $error ||= "can't change uid"; } if ( $old_gid != $new_gid ) { $error ||= "can't change gid"; } if ( $old_dir ne $new_dir ) { $error ||= "can't change dir"; } #if ( join("\n", sort @old_radius_groups) ne # join("\n", sort @new_radius_groups) ) { # $error ||= "can't change RADIUS groups"; #} } return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')' if $error; $self->shellcommands_queue( $new->svcnum, user => $self->option('user')||'root', host => $self->machine, command => eval(qq("$command")), stdin_string => eval(qq("$stdin")), ); } #a good idea to queue anything that could fail or take any time sub shellcommands_queue { my( $self, $svcnum ) = (shift, shift); my $queue = new FS::queue { 'svcnum' => $svcnum, 'job' => "FS::part_export::shellcommands::ssh_cmd", }; $queue->insert( @_ ); } sub ssh_cmd { #subroutine, not method use Net::SSH '0.08'; &Net::SSH::ssh_cmd( { @_ } ); } #sub shellcommands_insert { #subroutine, not method #} #sub shellcommands_replace { #subroutine, not method #} #sub shellcommands_delete { #subroutine, not method #} 1;