1 package FS::part_export::shellcommands;
3 use vars qw(@ISA %info);
5 use String::ShellQuote;
8 @ISA = qw(FS::part_export);
10 tie my %options, 'Tie::IxHash',
11 'user' => { label=>'Remote username', default=>'root' },
12 'useradd' => { label=>'Insert command',
13 default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username'
14 #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir'
16 'useradd_stdin' => { label=>'Insert command STDIN',
20 'userdel' => { label=>'Delete command',
21 default=>'userdel -r $username',
22 #default=>'rm -rf $dir',
24 'userdel_stdin' => { label=>'Delete command STDIN',
28 'usermod' => { label=>'Modify command',
29 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',
30 #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '.
31 # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '.
32 # 'find . -depth -print | cpio -pdm $new_dir; '.
33 # 'chmod u-t $new_dir; chown -R $uid.$gid $new_dir; '.
37 'usermod_stdin' => { label=>'Modify command STDIN',
41 'usermod_pwonly' => { label=>'Disallow username, domain, uid, gid, dir and RADIUS group changes',
44 'suspend' => { label=>'Suspension command',
45 default=>'usermod -L $username',
47 'suspend_stdin' => { label=>'Suspension command STDIN',
50 'unsuspend' => { label=>'Unsuspension command',
51 default=>'usermod -U $username',
53 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
56 'crypt' => { label => 'Default password encryption',
57 type=>'select', options=>[qw(crypt md5)],
65 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)',
66 'options' => \%options,
69 Run remote commands via SSH. Usernames are considered unique (also see
70 shellcommands_withdomain). You probably want this if the commands you are
71 running will not accept a domain as a parameter. You will need to
72 <a href="../docs/ssh.html">setup SSH for unattended operation</a>.
74 <BR><BR>Use these buttons for some useful presets:
77 <INPUT TYPE="button" VALUE="Linux" onClick='
78 this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
79 this.form.useradd_stdin.value = "";
80 this.form.userdel.value = "userdel -r $username";
81 this.form.userdel_stdin.value="";
82 this.form.usermod.value = "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";
83 this.form.usermod_stdin.value = "";
84 this.form.suspend.value = "usermod -L $username";
85 this.form.suspend_stdin.value="";
86 this.form.unsuspend.value = "usermod -U $username";
87 this.form.unsuspend_stdin.value="";
90 <INPUT TYPE="button" VALUE="FreeBSD before 4.10 / 5.3" onClick='
91 this.form.useradd.value = "lockf /etc/passwd.lock pw useradd $username -d $dir -m -s $shell -u $uid -c $finger -h 0";
92 this.form.useradd_stdin.value = "$_password\n";
93 this.form.userdel.value = "lockf /etc/passwd.lock pw userdel $username -r"; this.form.userdel_stdin.value="";
94 this.form.usermod.value = "lockf /etc/passwd.lock pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -c $new_finger -h 0";
95 this.form.usermod_stdin.value = "$new__password\n"; this.form.suspend.value = "lockf /etc/passwd.lock pw lock $username";
96 this.form.suspend_stdin.value="";
97 this.form.unsuspend.value = "lockf /etc/passwd.lock pw unlock $username"; this.form.unsuspend_stdin.value="";
99 Note: On FreeBSD versions before 5.3 and 4.10 (4.10 is after 4.9, not
100 4.1!), due to deficient locking in pw(1), you must disable the chpass(1),
101 chsh(1), chfn(1), passwd(1), and vipw(1) commands, or replace them with
102 wrappers that prepend "lockf /etc/passwd.lock". Alternatively, apply the
104 <A HREF="http://www.freebsd.org/cgi/query-pr.cgi?pr=23501">FreeBSD PR#23501</A>
105 and use the "FreeBSD 4.10 / 5.3 or later" button below.
107 <INPUT TYPE="button" VALUE="FreeBSD 4.10 / 5.3 or later" onClick='
108 this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
109 this.form.useradd_stdin.value = "$_password\n";
110 this.form.userdel.value = "pw userdel $username -r";
111 this.form.userdel_stdin.value="";
112 this.form.usermod.value = "pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -c $new_finger -h 0";
113 this.form.usermod_stdin.value = "$new__password\n";
114 this.form.suspend.value = "pw lock $username";
115 this.form.suspend_stdin.value="";
116 this.form.unsuspend.value = "pw unlock $username";
117 this.form.unsuspend_stdin.value="";
120 <INPUT TYPE="button" VALUE="NetBSD/OpenBSD" onClick='
121 this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
122 this.form.useradd_stdin.value = "";
123 this.form.userdel.value = "userdel -r $username";
124 this.form.userdel_stdin.value="";
125 this.form.usermod.value = "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";
126 this.form.usermod_stdin.value = "";
127 this.form.suspend.value = "";
128 this.form.suspend_stdin.value="";
129 this.form.unsuspend.value = "";
130 this.form.unsuspend_stdin.value="";
133 <INPUT TYPE="button" VALUE="Just maintain directories (use with sysvshell or bsdshell)" onClick='
134 this.form.useradd.value = "cp -pr /etc/skel $dir; chown -R $uid.$gid $dir"; this.form.useradd_stdin.value = "";
135 this.form.usermod.value = "[ -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 $new_uid.$new_gid $new_dir; rm -rf $old_dir )";
136 this.form.usermod_stdin.value = "";
137 this.form.userdel.value = "rm -rf $dir";
138 this.form.userdel_stdin.value="";
139 this.form.suspend.value = "";
140 this.form.suspend_stdin.value="";
141 this.form.unsuspend.value = "";
142 this.form.unsuspend_stdin.value="";
146 The following variables are available for interpolation (prefixed with new_ or
147 old_ for replace operations):
149 <LI><code>$username</code>
150 <LI><code>$_password</code>
151 <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes)
152 <LI><code>$crypt_password</code> - encrypted password, already quoted for the shell (do not add additional quotes)
153 <LI><code>$uid</code>
154 <LI><code>$gid</code>
155 <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)
156 <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes)
157 <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes)
158 <LI><code>$dir</code> - home directory
159 <LI><code>$shell</code>
160 <LI><code>$quota</code>
161 <LI><code>@radius_groups</code>
162 <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
167 sub rebless { shift; }
171 $self->_export_command('useradd', @_);
176 $self->_export_command('userdel', @_);
179 sub _export_suspend {
181 $self->_export_command_or_super('suspend', @_);
184 sub _export_unsuspend {
186 $self->_export_command_or_super('unsuspend', @_);
189 sub _export_command_or_super {
190 my($self, $action) = (shift, shift);
191 if ( $self->option($action) =~ /^\s*$/ ) {
192 my $method = "SUPER::_export_$action";
195 $self->_export_command($action, @_);
200 sub _export_command {
201 my ( $self, $action, $svc_acct) = (shift, shift, shift);
202 my $command = $self->option($action);
203 return '' if $command =~ /^\s*$/;
204 my $stdin = $self->option($action."_stdin");
209 ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
212 foreach my $acct_snarf ( $svc_acct->acct_snarf ) {
213 ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) )
214 foreach qw( machine username _password );
219 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
221 $email = ( grep { $_ !~ /^(POST|FAX)$/ } $cust_pkg->cust_main->invoicing_list )[0];
226 $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
227 ($first, $last ) = ( $1, $2 );
228 $first = shell_quote $first;
229 $last = shell_quote $last;
230 $finger = shell_quote $finger;
231 $quoted_password = shell_quote $_password;
232 $domain = $svc_acct->domain;
235 shell_quote( $svc_acct->crypt_password( $self->option('crypt') ) );
237 @radius_groups = $svc_acct->radius_groups;
239 $self->shellcommands_queue( $svc_acct->svcnum,
240 user => $self->option('user')||'root',
241 host => $self->machine,
242 command => eval(qq("$command")),
243 stdin_string => eval(qq("$stdin")),
247 sub _export_replace {
248 my($self, $new, $old ) = (shift, shift, shift);
249 my $command = $self->option('usermod');
250 my $stdin = $self->option('usermod_stdin');
254 ${"old_$_"} = $old->getfield($_) foreach $old->fields;
255 ${"new_$_"} = $new->getfield($_) foreach $new->fields;
257 $new_finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
258 ($new_first, $new_last ) = ( $1, $2 );
259 $new_first = shell_quote $new_first;
260 $new_last = shell_quote $new_last;
261 $new_finger = shell_quote $new_finger;
262 $quoted_new__password = shell_quote $new__password; #old, wrong?
263 $new_quoted_password = shell_quote $new__password; #new, better?
264 $old_domain = $old->domain;
265 $new_domain = $new->domain;
267 $new_crypt_password =
268 shell_quote( $new->crypt_password( $self->option('crypt') ) );
270 @old_radius_groups = $old->radius_groups;
271 @new_radius_groups = $new->radius_groups;
273 if ( $self->option('usermod_pwonly') ) {
275 if ( $old_username ne $new_username ) {
276 $error ||= "can't change username";
278 if ( $old_domain ne $new_domain ) {
279 $error ||= "can't change domain";
281 if ( $old_uid != $new_uid ) {
282 $error ||= "can't change uid";
284 if ( $old_gid != $new_gid ) {
285 $error ||= "can't change gid";
287 if ( $old_dir ne $new_dir ) {
288 $error ||= "can't change dir";
290 if ( join("\n", sort @old_radius_groups) ne
291 join("\n", sort @new_radius_groups) ) {
292 $error ||= "can't change RADIUS groups";
294 return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
297 $self->shellcommands_queue( $new->svcnum,
298 user => $self->option('user')||'root',
299 host => $self->machine,
300 command => eval(qq("$command")),
301 stdin_string => eval(qq("$stdin")),
305 #a good idea to queue anything that could fail or take any time
306 sub shellcommands_queue {
307 my( $self, $svcnum ) = (shift, shift);
308 my $queue = new FS::queue {
310 'job' => "FS::part_export::shellcommands::ssh_cmd",
312 $queue->insert( @_ );
315 sub ssh_cmd { #subroutine, not method
317 &Net::SSH::ssh_cmd( { @_ } );
320 #sub shellcommands_insert { #subroutine, not method
322 #sub shellcommands_replace { #subroutine, not method
324 #sub shellcommands_delete { #subroutine, not method