1 package FS::part_export::shellcommands;
3 use vars qw(@ISA %info @saltset);
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 -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 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',
61 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)',
62 'options' => \%options,
65 Run remote commands via SSH. Usernames are considered unique (also see
66 shellcommands_withdomain). You probably want this if the commands you are
67 running will not accept a domain as a parameter. You will need to
68 <a href="../docs/ssh.html">setup SSH for unattended operation</a>.
70 <BR><BR>Use these buttons for some useful presets:
73 <INPUT TYPE="button" VALUE="Linux" onClick='
74 this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
75 this.form.useradd_stdin.value = "";
76 this.form.userdel.value = "userdel -r $username";
77 this.form.userdel_stdin.value="";
78 this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username";
79 this.form.usermod_stdin.value = "";
80 this.form.suspend.value = "usermod -L $username";
81 this.form.suspend_stdin.value="";
82 this.form.unsuspend.value = "usermod -U $username";
83 this.form.unsuspend_stdin.value="";
86 <INPUT TYPE="button" VALUE="FreeBSD before 4.10 / 5.3" onClick='
87 this.form.useradd.value = "lockf /etc/passwd.lock pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
88 this.form.useradd_stdin.value = "$_password\n";
89 this.form.userdel.value = "lockf /etc/passwd.lock pw userdel $username -r"; this.form.userdel_stdin.value="";
90 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 -c $new_finger -h 0";
91 this.form.usermod_stdin.value = "$new__password\n"; this.form.suspend.value = "lockf /etc/passwd.lock pw lock $username";
92 this.form.suspend_stdin.value="";
93 this.form.unsuspend.value = "lockf /etc/passwd.lock pw unlock $username"; this.form.unsuspend_stdin.value="";
95 Note: On FreeBSD versions before 5.3 and 4.10 (4.10 is after 4.9, not
96 4.1!), due to deficient locking in pw(1), you must disable the chpass(1),
97 chsh(1), chfn(1), passwd(1), and vipw(1) commands, or replace them with
98 wrappers that prepend "lockf /etc/passwd.lock". Alternatively, apply the
100 <A HREF="http://www.freebsd.org/cgi/query-pr.cgi?pr=23501">FreeBSD PR#23501</A>
101 and use the "FreeBSD 4.10 / 5.3 or later" button below.
103 <INPUT TYPE="button" VALUE="FreeBSD 4.10 / 5.3 or later" onClick='
104 this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
105 this.form.useradd_stdin.value = "$_password\n";
106 this.form.userdel.value = "pw userdel $username -r";
107 this.form.userdel_stdin.value="";
108 this.form.usermod.value = "pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -c $new_finger -h 0";
109 this.form.usermod_stdin.value = "$new__password\n";
110 this.form.suspend.value = "pw lock $username";
111 this.form.suspend_stdin.value="";
112 this.form.unsuspend.value = "pw unlock $username";
113 this.form.unsuspend_stdin.value="";
116 <INPUT TYPE="button" VALUE="NetBSD/OpenBSD" onClick='
117 this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
118 this.form.useradd_stdin.value = "";
119 this.form.userdel.value = "userdel -r $username";
120 this.form.userdel_stdin.value="";
121 this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username";
122 this.form.usermod_stdin.value = "";
123 this.form.suspend.value = "";
124 this.form.suspend_stdin.value="";
125 this.form.unsuspend.value = "";
126 this.form.unsuspend_stdin.value="";
129 <INPUT TYPE="button" VALUE="Just maintain directories (use with sysvshell or bsdshell)" onClick='
130 this.form.useradd.value = "cp -pr /etc/skel $dir; chown -R $uid.$gid $dir"; this.form.useradd_stdin.value = "";
131 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 )";
132 this.form.usermod_stdin.value = "";
133 this.form.userdel.value = "rm -rf $dir";
134 this.form.userdel_stdin.value="";
135 this.form.suspend.value = "";
136 this.form.suspend_stdin.value="";
137 this.form.unsuspend.value = "";
138 this.form.unsuspend_stdin.value="";
142 The following variables are available for interpolation (prefixed with new_ or
143 old_ for replace operations):
145 <LI><code>$username</code>
146 <LI><code>$_password</code>
147 <LI><code>$quoted_password</code> - unencrypted password quoted for the shell
148 <LI><code>$crypt_password</code> - encrypted password
149 <LI><code>$uid</code>
150 <LI><code>$gid</code>
151 <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)
152 <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes)
153 <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes)
154 <LI><code>$dir</code> - home directory
155 <LI><code>$shell</code>
156 <LI><code>$quota</code>
157 <LI><code>@radius_groups</code>
158 <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
163 @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
165 sub rebless { shift; }
169 $self->_export_command('useradd', @_);
174 $self->_export_command('userdel', @_);
177 sub _export_suspend {
179 $self->_export_command('suspend', @_);
182 sub _export_unsuspend {
184 $self->_export_command('unsuspend', @_);
187 sub _export_command {
188 my ( $self, $action, $svc_acct) = (shift, shift, shift);
189 my $command = $self->option($action);
190 return '' if $command =~ /^\s*$/;
191 my $stdin = $self->option($action."_stdin");
196 ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
199 foreach my $acct_snarf ( $svc_acct->acct_snarf ) {
200 ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) )
201 foreach qw( machine username _password );
206 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
208 $email = ( grep { $_ ne 'POST' } $cust_pkg->cust_main->invoicing_list )[0];
213 $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
214 ($first, $last ) = ( $1, $2 );
215 $first = shell_quote $first;
216 $last = shell_quote $last;
217 $finger = shell_quote $finger;
218 $quoted_password = shell_quote $_password;
219 $domain = $svc_acct->domain;
221 #eventually should check a "password-encoding" field
222 if ( length($svc_acct->_password) == 13
223 || $svc_acct->_password =~ /^\$(1|2a?)\$/ ) {
224 $crypt_password = shell_quote $svc_acct->_password;
226 $crypt_password = crypt(
227 $svc_acct->_password,
228 $saltset[int(rand(64))].$saltset[int(rand(64))]
232 @radius_groups = $svc_acct->radius_groups;
234 $self->shellcommands_queue( $svc_acct->svcnum,
235 user => $self->option('user')||'root',
236 host => $self->machine,
237 command => eval(qq("$command")),
238 stdin_string => eval(qq("$stdin")),
242 sub _export_replace {
243 my($self, $new, $old ) = (shift, shift, shift);
244 my $command = $self->option('usermod');
245 my $stdin = $self->option('usermod_stdin');
249 ${"old_$_"} = $old->getfield($_) foreach $old->fields;
250 ${"new_$_"} = $new->getfield($_) foreach $new->fields;
252 $new_finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
253 ($new_first, $new_last ) = ( $1, $2 );
254 $new_first = shell_quote $new_first;
255 $new_last = shell_quote $new_last;
256 $new_finger = shell_quote $new_finger;
257 $quoted_new__password = shell_quote $new__password; #old, wrong?
258 $new_quoted_password = shell_quote $new__password; #new, better?
259 $old_domain = $old->domain;
260 $new_domain = $new->domain;
262 #eventuall should check a "password-encoding" field
263 if ( length($new->_password) == 13
264 || $new->_password =~ /^\$(1|2a?)\$/ ) {
265 $new_crypt_password = shell_quote $new->_password;
267 $new_crypt_password =
268 crypt( $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
272 @old_radius_groups = $old->radius_groups;
273 @new_radius_groups = $new->radius_groups;
275 if ( $self->option('usermod_pwonly') ) {
277 if ( $old_username ne $new_username ) {
278 $error ||= "can't change username";
280 if ( $old_domain ne $new_domain ) {
281 $error ||= "can't change domain";
283 if ( $old_uid != $new_uid ) {
284 $error ||= "can't change uid";
286 if ( $old_dir ne $new_dir ) {
287 $error ||= "can't change dir";
289 if ( join("\n", sort @old_radius_groups) ne
290 join("\n", sort @new_radius_groups) ) {
291 $error ||= "can't change RADIUS groups";
293 return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
296 $self->shellcommands_queue( $new->svcnum,
297 user => $self->option('user')||'root',
298 host => $self->machine,
299 command => eval(qq("$command")),
300 stdin_string => eval(qq("$stdin")),
304 #a good idea to queue anything that could fail or take any time
305 sub shellcommands_queue {
306 my( $self, $svcnum ) = (shift, shift);
307 my $queue = new FS::queue {
309 'job' => "FS::part_export::shellcommands::ssh_cmd",
311 $queue->insert( @_ );
314 sub ssh_cmd { #subroutine, not method
316 &Net::SSH::ssh_cmd( { @_ } );
319 #sub shellcommands_insert { #subroutine, not method
321 #sub shellcommands_replace { #subroutine, not method
323 #sub shellcommands_delete { #subroutine, not method