2 # Functions for storing custom commands
4 BEGIN { push(@INC, ".."); };
7 %access = &get_module_acl();
10 # Returns a list of all custom commands
14 local $mcd = $module_info{'usermin'} ? $config{'webmin_config'}
15 : $module_config_directory;
17 while($f = readdir(DIR)) {
19 if ($f =~ /^(\d+)\.cmd$/) {
20 # Read custom-command file
21 $cmd{'file'} = "$mcd/$f";
23 open(FILE, $cmd{'file'});
24 chop($cmd{'cmd'} = <FILE>);
25 chop($cmd{'desc'} = <FILE>);
26 local @o = split(/\s+/, <FILE>);
28 $cmd{'raw'} = int($o[1]);
29 $cmd{'su'} = int($o[2]);
30 $cmd{'order'} = int($o[3]);
31 $cmd{'noshow'} = int($o[4]);
32 $cmd{'usermin'} = int($o[5]);
33 $cmd{'timeout'} = int($o[6]);
34 $cmd{'clear'} = int($o[7]);
35 $cmd{'format'} = $o[8] eq '-' ? undef : $o[8];
37 elsif ($f =~ /^(\d+)\.edit$/) {
38 # Read file-editor file
39 $cmd{'file'} = "$mcd/$f";
41 open(FILE, $cmd{'file'});
42 chop($cmd{'edit'} = <FILE>);
43 chop($cmd{'desc'} = <FILE>);
44 chop($cmd{'user'} = <FILE>);
45 chop($cmd{'group'} = <FILE>);
46 chop($cmd{'perms'} = <FILE>);
47 chop($cmd{'before'} = <FILE>);
48 chop($cmd{'after'} = <FILE>);
49 chop($cmd{'order'} = <FILE>);
50 $cmd{'order'} = int($cmd{'order'});
51 chop($cmd{'usermin'} = <FILE>);
52 chop($cmd{'envs'} = <FILE>);
53 chop($cmd{'beforeedit'} = <FILE>);
55 elsif ($f =~ /^(\d+)\.sql$/) {
57 $cmd{'file'} = "$mcd/$f";
59 open(FILE, $cmd{'file'});
60 chop($cmd{'desc'} = <FILE>);
61 chop($cmd{'type'} = <FILE>);
62 chop($cmd{'db'} = <FILE>);
63 chop($cmd{'user'} = <FILE>);
64 chop($cmd{'pass'} = <FILE>);
65 chop($cmd{'host'} = <FILE>);
66 chop($cmd{'sql'} = <FILE>);
67 $cmd{'sql'} =~ s/\t/\n/g;
68 chop($cmd{'order'} = <FILE>);
74 local @a = split(/:/, $_, 5);
75 local ($quote, $must) = split(/,/, $a[3]);
76 push(@{$cmd{'args'}}, { 'name' => $a[0],
79 'quote' => int($quote),
84 $cmd{'index'} = scalar(@rv);
85 open(HTML, "$mcd/$cmd{'id'}.html");
91 # Read cluster hosts file
92 open(CLUSTER, "$mcd/$cmd{'id'}.hosts");
95 push(@{$cmd{'hosts'}}, $_);
106 # sort_commands(&command, ...)
107 # Sorts a list of custom commands by the user-defined order
111 if ($config{'sort'}) {
112 @cust = sort { lc($a->{$config{'sort'}}) cmp
113 lc($b->{$config{'sort'}}) } @cust;
116 @cust = sort { local $o = $b->{'order'} <=> $a->{'order'};
117 $o ? $o : $a->{'id'} <=> $b->{'id'} } @cust;
123 # Returns the command with some ID
126 local ($id, $idx) = @_;
127 local @cmds = &list_commands();
130 ($cmd) = grep { $_->{'id'} eq $id } &list_commands();
138 # save_command(&command)
144 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.edit");
145 &print_tempfile(FILE, $c->{'edit'},"\n");
146 &print_tempfile(FILE, $c->{'desc'},"\n");
147 &print_tempfile(FILE, $c->{'user'},"\n");
148 &print_tempfile(FILE, $c->{'group'},"\n");
149 &print_tempfile(FILE, $c->{'perms'},"\n");
150 &print_tempfile(FILE, $c->{'before'},"\n");
151 &print_tempfile(FILE, $c->{'after'},"\n");
152 &print_tempfile(FILE, $c->{'order'},"\n");
153 &print_tempfile(FILE, $c->{'usermin'},"\n");
154 &print_tempfile(FILE, $c->{'envs'},"\n");
155 &print_tempfile(FILE, $c->{'beforeedit'},"\n");
157 elsif ($c->{'sql'}) {
158 # Save an SQL command
159 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.sql");
160 &print_tempfile(FILE, $c->{'desc'},"\n");
161 &print_tempfile(FILE, $c->{'type'},"\n");
162 &print_tempfile(FILE, $c->{'db'},"\n");
163 &print_tempfile(FILE, $c->{'user'},"\n");
164 &print_tempfile(FILE, $c->{'pass'},"\n");
165 &print_tempfile(FILE, $c->{'host'},"\n");
166 local $sql = $c->{'sql'};
168 &print_tempfile(FILE, $sql,"\n");
169 &print_tempfile(FILE, $c->{'order'},"\n");
172 # Save a custom command
173 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.cmd");
174 &print_tempfile(FILE, $c->{'cmd'},"\n");
175 &print_tempfile(FILE, $c->{'desc'},"\n");
176 &print_tempfile(FILE,
177 $c->{'user'}," ",int($c->{'raw'})," ",int($c->{'su'})," ",
178 int($c->{'order'})," ",int($c->{'noshow'})," ",
179 int($c->{'usermin'})," ",int($c->{'timeout'})," ",
180 int($c->{'clear'})," ",($c->{'format'} || "-"),"\n");
185 foreach $a (@{$c->{'args'}}) {
186 &print_tempfile(FILE, $a->{'name'},":",$a->{'type'},":",
187 $a->{'opts'},":",int($a->{'quote'}),",",int($a->{'must'}),":",
190 &close_tempfile(FILE);
192 # Save HTML description file
193 &lock_file("$module_config_directory/$c->{'id'}.html");
194 if ($cmd->{'html'}) {
195 &open_tempfile(HTML, ">$module_config_directory/$c->{'id'}.html");
196 &print_tempfile(HTML, $cmd->{'html'});
197 &close_tempfile(HTML);
200 unlink("$module_config_directory/$c->{'id'}.html");
202 &unlock_file("$module_config_directory/$c->{'id'}.html");
205 &lock_file("$module_config_directory/$c->{'id'}.hosts");
206 if (@{$cmd->{'hosts'}}) {
207 &open_tempfile(CLUSTER, ">$module_config_directory/$c->{'id'}.hosts");
208 foreach my $h (@{$cmd->{'hosts'}}) {
209 &print_tempfile(CLUSTER, "$h\n");
211 &close_tempfile(CLUSTER);
214 unlink("$module_config_directory/$c->{'id'}.hosts");
216 &unlock_file("$module_config_directory/$c->{'id'}.hosts");
219 # delete_command(&command)
222 local $f = "$module_config_directory/$_[0]->{'id'}".
223 ($_[0]->{'edit'} ? ".edit" : $_[0]->{'sql'} ? ".sql" : ".cmd");
229 local $hf = "$module_config_directory/$_[0]->{'id'}.html";
236 # Delete cluster file
237 local $cf = "$module_config_directory/$_[0]->{'id'}.hosts";
247 if ($module_info{'usermin'}) {
248 # Only modules marked as for Usermin are considered
249 return 0 if (!$_[0]->{'usermin'});
251 # Check detailed access control list (if any)
252 return 1 if (!$config{'access'});
253 local @uinfo = @remote_user_info;
254 @uinfo = getpwnam($remote_user) if (!@uinfo);
256 foreach $l (split(/\t/, $config{'access'})) {
257 if ($l =~ /^(\S+):\s*(.*)$/) {
258 local ($user, $ids) = ($1, $2);
260 if ($user =~ /^\@(.*)$/) {
261 # Check if user is in group
262 local @ginfo = getgrnam($1);
264 if (@ginfo && ($ginfo[2] == $uinfo[3] ||
265 &indexof($remote_user,
266 split(/\s+/, $ginfo[3])) >= 0));
268 elsif ($user eq $remote_user || $user eq "*") {
272 # Rule is for this user - check list
273 local @ids = split(/\s+/, $ids);
276 return 1 if ($d eq '*' ||
277 $_[0]->{'id'} eq $d);
278 return 0 if ("!".$_[0]->{'id'} eq $d);
287 # Just use Webmin user's list of databases
290 return 1 if ($access{'cmds'} eq '*');
291 local @cmds = split(/\s+/, $access{'cmds'});
293 $found++ if ($c eq $_[0]->{'id'});
295 return $cmds[0] eq '!' ? !$found : $found;
299 # read_opts_file(file)
304 if ($file !~ /^\// && $file !~ /\|\s*$/) {
305 local @uinfo = getpwnam($remote_user);
307 $file = "$uinfo[7]/$file";
313 if (/^"([^"]*)"\s+"([^"]*)"$/) {
314 push(@rv, [ $1, $2 ]);
316 elsif (/^"([^"]*)"$/) {
317 push(@rv, [ $1, $1 ]);
319 elsif (/^(\S+)\s+(\S.*)/) {
320 push(@rv, [ $1, $2 ]);
323 push(@rv, [ $_, $_ ]);
330 # show_params_inputs(&command, no-quote, editor-mode)
331 sub show_params_inputs
333 local ($cmd, $noquote, $editor) = @_;
335 local $ptable = &ui_columns_start([
336 $text{'edit_name'}, $text{'edit_desc'}, $text{'edit_type'},
337 $noquote ? ( ) : ( $text{'edit_quote'} ),
339 ], 100, 0, undef, undef);
340 local @a = (@{$cmd->{'args'}}, { });
341 for(my $i=0; $i<@a; $i++) {
343 push(@cols, &ui_textbox("name_$i", $a[$i]->{'name'}, 10));
344 push(@cols, &ui_textbox("desc_$i", $a[$i]->{'desc'}, 40));
346 for(my $j=0; $text{"edit_type$j"}; $j++) {
348 ($j == 7 || $j == 8 || $j == 10 || $j == 11));
349 push(@opts, [ $j, $text{"edit_type$j"} ]);
351 push(@cols, &ui_select("type_$i", $a[$i]->{'type'}, \@opts)." ".
352 &ui_textbox("opts_$i", $a[$i]->{'opts'}, 40));
354 push(@cols, &ui_yesno_radio("quote_$i",
355 int($a[$i]->{'quote'})));
357 push(@cols, &ui_yesno_radio("must_$i",
358 int($a[$i]->{'must'})));
359 $ptable .= &ui_columns_row(\@cols);
362 $ptable .= &ui_columns_end();
366 # parse_params_inputs(&command)
367 sub parse_params_inputs
370 $cmd->{'args'} = [ ];
372 for($i=0; defined($name = $in{"name_$i"}); $i++) {
374 push(@{$cmd->{'args'}}, { 'name' => $name,
375 'desc' => $in{"desc_$i"},
376 'type' => $in{"type_$i"},
377 'quote' => int($in{"quote_$i"}),
378 'must' => int($in{"must_$i"}),
379 'opts' => $in{"opts_$i"} });
384 # set_parameter_envs(&command, command-str, &uinfo, [set-in], [skip-menu-check])
385 # Sets $ENV variables based on parameter inputs, and returns the list of
386 # environment variable commands, the export commands, the command string,
387 # and the command string to display.
388 sub set_parameter_envs
390 local ($cmd, $str, $uinfo, $setin, $skipfound) = @_;
392 local $displaystr = $str;
393 local ($env, $export, @vals);
394 foreach my $a (@{$cmd->{'args'}}) {
395 my $n = $a->{'name'};
397 if ($a->{'type'} == 0 || $a->{'type'} == 5 ||
398 $a->{'type'} == 6 || $a->{'type'} == 8) {
401 elsif ($a->{'type'} == 11) {
406 elsif ($a->{'type'} == 1 || $a->{'type'} == 2) {
407 (@u = getpwnam($setin->{$n})) || &error($text{'run_euser'});
408 $rv = $a->{'type'} == 1 ? $setin->{$n} : $u[2];
410 elsif ($a->{'type'} == 3 || $a->{'type'} == 4) {
411 (@g = getgrnam($setin->{$n})) || &error($text{'run_egroup'});
412 $rv = $a->{'type'} == 3 ? $setin->{$n} : $g[2];
414 elsif ($a->{'type'} == 7) {
415 $rv = $setin->{$n} ? $a->{'opts'} : "";
417 elsif ($a->{'type'} == 9) {
419 foreach my $l (&read_opts_file($a->{'opts'})) {
420 $found++ if ($l->[0] eq $setin->{$n});
422 $found || $skipfound || &error($text{'run_eopt'});
425 elsif ($a->{'type'} == 10) {
426 $setin->{$n} || &error($text{'run_eupload'});
427 if ($setin->{$n."_filename"} =~ /([^\/\\]+$)/ && $1) {
428 $rv = &transname("$1");
433 &open_tempfile(TEMP, ">$rv");
434 &print_tempfile(TEMP, $setin->{$n});
435 &close_tempfile(TEMP);
436 chown($uinfo->[2], $uinfo->[3], $rv);
439 elsif ($a->{'type'} == 12 || $a->{'type'} == 13 || $a->{'type'} == 14) {
441 if ($a->{'type'} == 14) {
442 @vals = split(/\r?\n/, $setin->{$n});
445 @vals = split(/\0/, $setin->{$n});
447 local @opts = &read_opts_file($a->{'opts'});
448 foreach my $v (@vals) {
450 foreach my $l (@opts) {
451 $found++ if ($l->[0] eq $v);
453 $found || $skipfound || &error($text{'run_eopt'});
455 $rv = join(" ", @vals);
457 elsif ($a->{'type'} == 15) {
458 $rv = $setin->{$n."_year"}."-".
459 $setin->{$n."_month"}."-".
462 if ($rv eq '' && $a->{'must'} && $a->{'type'} != 7) {
463 &error(&text('run_emust', $a->{'desc'}));
466 $env .= "$n='$rv'\n";
469 $str =~ s/\$$n/"\$$n"/g;
470 $displaystr =~ s/\$$n/"$rv"/g;
473 $displaystr =~ s/\$$n/$rv/g;
477 return ($env, $export, $str, $displaystr, \@vals);
481 # Returns a list of DBI driver details, which are actually installed
484 local @rv = ( { 'name' => 'MySQL',
486 'dbparam' => 'database' },
487 { 'name' => 'PostgreSQL',
489 'dbparam' => 'dbname' },
491 @rv = grep { eval "use DBD::$_->{'driver'}"; !$@ } @rv;
496 # Returns a list of servers that a command can run on
499 if (&foreign_installed("servers")) {
500 &foreign_require("servers", "servers-lib.pl");
501 @servers = grep { $_->{'user'} } &servers::list_servers();
503 return ( { 'id' => 0, 'desc' => $text{'edit_this'} }, @servers);
506 return ( { 'id' => 0, 'desc' => $text{'edit_this'} } );
509 # execute_custom_command(&command, environment, exports, string, print-output)
510 # Runs some command, and returns the bytes, output and timeout flag
511 sub execute_custom_command
513 local ($cmd, $env, $export, $str, $print) = @_;
514 &foreign_require("proc", "proc-lib.pl");
516 &clean_environment() if ($cmd->{'clear'});
518 local $outtemp = &transname();
519 open(OUTTEMP, ">$outtemp");
520 local $fh = $print ? STDOUT : *OUTTEMP;
522 local $temp = &transname();
523 &open_tempfile(TEMP, ">$temp");
524 &print_tempfile(TEMP, "#!/bin/sh\n");
525 &print_tempfile(TEMP, $env);
526 &print_tempfile(TEMP, "export $export\n") if ($export);
527 &print_tempfile(TEMP, "$str\n");
528 &close_tempfile(TEMP);
530 $got = &proc::safe_process_exec(
531 &command_as_user($user, 1, $temp), 0, 0,
532 $fh, undef, !$cmd->{'raw'} && !$cmd->{'format'}, 0,
537 $got = &proc::safe_process_exec(
538 $str, $user_info[2], undef, $fh, undef,
539 !$cmd->{'raw'} && !$cmd->{'format'}, 0,
542 &reset_environment() if ($cmd->{'clear'});
544 local $rv = &read_file_contents($outtemp);
546 return ($got, $rv, $proc::safe_process_exec_timeout ? 1 : 0);
549 # show_parameter_input(&arg, formno)
550 # Returns HTML for a parameter input
551 sub show_parameter_input
553 local ($a, $form) = @_;
554 local $n = $a->{'name'};
555 local $v = $a->{'opts'};
556 if ($a->{'type'} != 9 && $a->{'type'} != 12 &&
557 $a->{'type'} != 13 && $a->{'type'} != 14) {
558 if ($v =~ /^"(.*)"$/ || $v =~ /^'(.*)'$/) {
562 elsif ($v =~ /^(.*)\s*\|$/ && $config{'params_cmd'}) {
564 $v = &backquote_command("$1 2>/dev/null </dev/null");
565 if ($a->{'type'} != 11) {
569 elsif ($v =~ /^\// && $config{'params_file'}) {
571 $v = &read_file_contents($v);
572 if ($a->{'type'} != 11) {
577 if ($a->{'type'} == 0) {
578 return &ui_textbox($n, $v, 30);
580 elsif ($a->{'type'} == 1 || $a->{'type'} == 2) {
581 return &ui_user_textbox($n, $v, $form);
583 elsif ($a->{'type'} == 3 || $a->{'type'} == 4) {
584 return &ui_group_textbox($n, $v, $form);
586 elsif ($a->{'type'} == 5 || $a->{'type'} == 6) {
587 return &ui_textbox($n, $v, 30)." ".
588 &file_chooser_button($n, $a->{'type'}-5, $form);
590 elsif ($a->{'type'} == 7) {
591 return &ui_yesno_radio($n, $v =~ /true|yes|1/ ? 1 : 0);
593 elsif ($a->{'type'} == 8) {
594 return &ui_password($n, $v, 30);
596 elsif ($a->{'type'} == 9) {
597 return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ]);
599 elsif ($a->{'type'} == 10) {
600 return &ui_upload($n, 30);
602 elsif ($a->{'type'} == 11) {
603 return &ui_textarea($n, $v, 4, 30);
605 elsif ($a->{'type'} == 12) {
606 return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ],
609 elsif ($a->{'type'} == 13) {
610 my @opts = &read_opts_file($a->{'opts'});
611 return &ui_select($n, undef, \@opts, scalar(@opts), 1);
613 elsif ($a->{'type'} == 14) {
614 my @opts = &read_opts_file($a->{'opts'});
615 return &ui_multi_select($n, [ ], \@opts, 5);
617 elsif ($a->{'type'} == 15) {
618 my ($year, $month, $day) = split(/\-/, $v);
619 return &ui_date_input($day, $month, $year,
620 $n."_day", $n."_month", $n."_year")." ".
621 &date_chooser_button($n."_day", $n."_month", $n."_year");
624 return "Unknown parameter type $a->{'type'}";