1# custom-lib.pl
2# Functions for storing custom commands
3
4BEGIN { push(@INC, ".."); };
5use WebminCore;
6&init_config();
7%access = &get_module_acl();
8
9# list_commands()
10# Returns a list of all custom commands
11sub list_commands
12{
13local (@rv, $f);
14local $mcd = $module_info{'usermin'} ? $config{'webmin_config'}
15				     : $module_config_directory;
16opendir(DIR, $mcd);
17while($f = readdir(DIR)) {
18	local %cmd;
19	if ($f =~ /^(\d+)\.cmd$/) {
20		# Read custom-command file
21		$cmd{'file'} = "$mcd/$f";
22		$cmd{'id'} = $1;
23		open(FILE, "<".$cmd{'file'});
24		chop($cmd{'cmd'} = <FILE>);
25		chop($cmd{'desc'} = <FILE>);
26		local @o = split(/\s+/, <FILE>);
27		$cmd{'user'} = $o[0];
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];
36		}
37	elsif ($f =~ /^(\d+)\.edit$/) {
38		# Read file-editor file
39		$cmd{'file'} = "$mcd/$f";
40		$cmd{'id'} = $1;
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>);
54		}
55	elsif ($f =~ /^(\d+)\.sql$/) {
56		# Read SQL file
57		$cmd{'file'} = "$mcd/$f";
58		$cmd{'id'} = $1;
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>);
69		}
70	if (%cmd) {
71		# Read common stuff
72		while(<FILE>) {
73			s/\r|\n//g;
74			local @a = split(/:/, $_, 5);
75			local ($quote, $must) = split(/,/, $a[3]);
76			push(@{$cmd{'args'}}, { 'name' => $a[0],
77						'type' => $a[1],
78						'opts' => $a[2],
79						'quote' => int($quote),
80						'must' => int($must),
81						'desc' => $a[4] });
82			}
83		close(FILE);
84		$cmd{'index'} = scalar(@rv);
85		open(HTML, "<$mcd/$cmd{'id'}.html");
86		while(<HTML>) {
87			$cmd{'html'} .= $_;
88			}
89		close(HTML);
90
91		# Read cluster hosts file
92		open(CLUSTER, "<$mcd/$cmd{'id'}.hosts");
93		while(<CLUSTER>) {
94			s/\r|\n//g;
95			push(@{$cmd{'hosts'}}, $_);
96			}
97		close(CLUSTER);
98
99		push(@rv, \%cmd);
100		}
101	}
102closedir(DIR);
103return @rv;
104}
105
106# sort_commands(&command, ...)
107# Sorts a list of custom commands by the user-defined order
108sub sort_commands
109{
110local @cust = @_;
111if ($config{'sort'}) {
112	@cust = sort { lc($a->{$config{'sort'}}) cmp
113		       lc($b->{$config{'sort'}}) } @cust;
114	}
115else {
116	@cust = sort { local $o = $b->{'order'} <=> $a->{'order'};
117		       $o ? $o : $a->{'id'} <=> $b->{'id'} } @cust;
118	}
119return @cust;
120}
121
122# get_command(id)
123# Returns the command with some ID
124sub get_command
125{
126local ($id, $idx) = @_;
127local @cmds = &list_commands();
128local $cmd;
129if ($id) {
130	($cmd) = grep { $_->{'id'} eq $id } &list_commands();
131	}
132else {
133	$cmd = $cmds[$idx];
134	}
135return $cmd;
136}
137
138# save_command(&command)
139sub save_command
140{
141local $c = $_[0];
142if ($c->{'edit'}) {
143	# Save a file editor
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");
156	}
157elsif ($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'};
167	$sql =~ s/\n/\t/g;
168	&print_tempfile(FILE, $sql,"\n");
169	&print_tempfile(FILE, $c->{'order'},"\n");
170	}
171else {
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");
181	}
182
183
184# Save parameters
185foreach $a (@{$c->{'args'}}) {
186	&print_tempfile(FILE, $a->{'name'},":",$a->{'type'},":",
187	   $a->{'opts'},":",int($a->{'quote'}),",",int($a->{'must'}),":",
188	   $a->{'desc'},"\n");
189	}
190&close_tempfile(FILE);
191
192# Save HTML description file
193&lock_file("$module_config_directory/$c->{'id'}.html");
194if ($cmd->{'html'}) {
195	&open_tempfile(HTML, ">$module_config_directory/$c->{'id'}.html");
196	&print_tempfile(HTML, $cmd->{'html'});
197	&close_tempfile(HTML);
198	}
199else {
200	unlink("$module_config_directory/$c->{'id'}.html");
201	}
202&unlock_file("$module_config_directory/$c->{'id'}.html");
203
204# Save cluster hosts
205&lock_file("$module_config_directory/$c->{'id'}.hosts");
206if (@{$cmd->{'hosts'}}) {
207	&open_tempfile(CLUSTER, ">$module_config_directory/$c->{'id'}.hosts");
208	foreach my $h (@{$cmd->{'hosts'}}) {
209		&print_tempfile(CLUSTER, "$h\n");
210		}
211	&close_tempfile(CLUSTER);
212	}
213else {
214	unlink("$module_config_directory/$c->{'id'}.hosts");
215	}
216&unlock_file("$module_config_directory/$c->{'id'}.hosts");
217}
218
219# delete_command(&command)
220sub delete_command
221{
222local $f = "$module_config_directory/$_[0]->{'id'}".
223	   ($_[0]->{'edit'} ? ".edit" : $_[0]->{'sql'} ? ".sql" : ".cmd");
224&lock_file($f);
225unlink($f);
226&unlock_file($f);
227
228# Delete HTML file
229local $hf = "$module_config_directory/$_[0]->{'id'}.html";
230if (-r $hf) {
231	&lock_file($hf);
232	unlink($hf);
233	&unlock_file($hf);
234	}
235
236# Delete cluster file
237local $cf = "$module_config_directory/$_[0]->{'id'}.hosts";
238if (-r $cf) {
239	&lock_file($cf);
240	unlink($cf);
241	&unlock_file($cf);
242	}
243}
244
245sub can_run_command
246{
247if ($module_info{'usermin'}) {
248	# Only modules marked as for Usermin are considered
249	return 0 if (!$_[0]->{'usermin'});
250
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);
255	local $l;
256	foreach $l (split(/\t/, $config{'access'})) {
257		if ($l =~ /^(\S+):\s*(.*)$/) {
258			local ($user, $ids) = ($1, $2);
259			local $applies;
260			if ($user =~ /^\@(.*)$/) {
261				# Check if user is in group
262				local @ginfo = getgrnam($1);
263				$applies++
264				   if (@ginfo && ($ginfo[2] == $uinfo[3] ||
265				       &indexof($remote_user,
266						split(/\s+/, $ginfo[3])) >= 0));
267				}
268			elsif ($user eq $remote_user || $user eq "*") {
269				$applies++;
270				}
271			if ($applies) {
272				# Rule is for this user - check list
273				local @ids = split(/\s+/, $ids);
274				local $d;
275				foreach $d (@ids) {
276					return 1 if ($d eq '*' ||
277						     $_[0]->{'id'} eq $d);
278					return 0 if ("!".$_[0]->{'id'} eq $d);
279					}
280				return 0;
281				}
282			}
283		}
284	return 0;
285	}
286else {
287	# Just use Webmin user's list of databases
288	local $c;
289	local $found;
290	return 1 if ($access{'cmds'} eq '*');
291	local @cmds = split(/\s+/, $access{'cmds'});
292	foreach $c (@cmds) {
293		$found++ if ($c eq $_[0]->{'id'});
294		}
295	return $cmds[0] eq '!' ? !$found : $found;
296	}
297}
298
299# read_opts_file(file)
300# Read the file containing possible menu options for a command
301sub read_opts_file
302{
303local @rv;
304local $file = $_[0];
305if ($file !~ /^\// && $file !~ /\|\s*$/) {
306	local @uinfo = getpwnam($remote_user);
307	if (@uinfo) {
308		$file = "$uinfo[7]/$file";
309		}
310	}
311my $h;
312$h = "<" if ($file =~ /^\// && $file !~ /\|\s*$/);
313open(FILE, "$h".$file);
314while(<FILE>) {
315	s/\r|\n//g;
316	next if (/^#/);
317	if (/^"([^"]*)"\s+"([^"]*)"$/) {
318		push(@rv, [ $1, $2 ]);
319		}
320	elsif (/^"([^"]*)"$/) {
321		push(@rv, [ $1, $1 ]);
322		}
323	elsif (/^(\S+)\s+(\S.*)/) {
324		push(@rv, [ $1, $2 ]);
325		}
326	else {
327		push(@rv, [ $_, $_ ]);
328		}
329	}
330close(FILE);
331return @rv;
332}
333
334# show_params_inputs(&command, no-quote, editor-mode)
335sub show_params_inputs
336{
337local ($cmd, $noquote, $editor) = @_;
338
339local $ptable = &ui_columns_start([
340	$text{'edit_name'}, $text{'edit_desc'}, $text{'edit_type'},
341	$noquote ? ( ) : ( $text{'edit_quote'} ),
342	$text{'edit_must'},
343	], 100, 0, undef, undef);
344local @a = (@{$cmd->{'args'}}, { });
345for(my $i=0; $i<@a; $i++) {
346	local @cols;
347	push(@cols, &ui_textbox("name_$i", $a[$i]->{'name'}, 10));
348	push(@cols, &ui_textbox("desc_$i", $a[$i]->{'desc'}, 40));
349	local @opts;
350	for(my $j=0; $text{"edit_type$j"}; $j++) {
351		next if ($editor &&
352			 ($j == 7 || $j == 8 || $j == 10 || $j == 11));
353		push(@opts, [ $j, $text{"edit_type$j"} ]);
354		}
355	push(@cols, &ui_select("type_$i", $a[$i]->{'type'}, \@opts)." ".
356		    &ui_textbox("opts_$i", $a[$i]->{'opts'}, 40));
357	if (!$noquote) {
358		push(@cols, &ui_yesno_radio("quote_$i",
359					    int($a[$i]->{'quote'})));
360		}
361	push(@cols, &ui_yesno_radio("must_$i",
362                                    int($a[$i]->{'must'})));
363	$ptable .= &ui_columns_row(\@cols);
364	}
365
366$ptable .= &ui_columns_end();
367print $ptable;
368}
369
370# parse_params_inputs(&command)
371sub parse_params_inputs
372{
373local ($cmd) = @_;
374$cmd->{'args'} = [ ];
375my ($i, $name);
376for($i=0; defined($name = $in{"name_$i"}); $i++) {
377	if ($name) {
378		if ($in{"type_$i"} == 9 || $in{"type_$i"} == 12 ||
379		    $in{"type_$i"} == 13 || $in{"type_$i"} == 14) {
380			$in{"opts_$i"} =~ /\|$/ || -r $in{"opts_$i"} ||
381				&error(&text('save_eopts', $i+1));
382			}
383		$in{"opts_$i"} =~ /:/ && &error(&text('save_eopts2', $i+1));
384		push(@{$cmd->{'args'}}, { 'name' => $name,
385					  'desc' => $in{"desc_$i"},
386					  'type' => $in{"type_$i"},
387					  'quote' => int($in{"quote_$i"}),
388					  'must' => int($in{"must_$i"}),
389					  'opts' => $in{"opts_$i"} });
390		}
391	}
392}
393
394# set_parameter_envs(&command, command-str, &uinfo, [set-in], [skip-menu-check])
395# Sets $ENV variables based on parameter inputs, and returns the list of
396# environment variable commands, the export commands, the command string,
397# and the command string to display.
398sub set_parameter_envs
399{
400local ($cmd, $str, $uinfo, $setin, $skipfound) = @_;
401$setin ||= \%in;
402local $displaystr = $str;
403local ($env, $export, @vals);
404foreach my $a (@{$cmd->{'args'}}) {
405	my $n = $a->{'name'};
406	my $rv;
407	if ($a->{'type'} == 0 || $a->{'type'} == 5 ||
408	    $a->{'type'} == 6 || $a->{'type'} == 8) {
409		$rv = $setin->{$n};
410		}
411	elsif ($a->{'type'} == 11) {
412		$rv = $setin->{$n};
413		$rv =~ s/\r//g;
414		$rv =~ s/\n/ /g;
415		}
416	elsif ($a->{'type'} == 1 || $a->{'type'} == 2) {
417		(@u = getpwnam($setin->{$n})) || &error($text{'run_euser'});
418		$rv = $a->{'type'} == 1 ? $setin->{$n} : $u[2];
419		}
420	elsif ($a->{'type'} == 3 || $a->{'type'} == 4) {
421		(@g = getgrnam($setin->{$n})) || &error($text{'run_egroup'});
422		$rv = $a->{'type'} == 3 ? $setin->{$n} : $g[2];
423		}
424	elsif ($a->{'type'} == 7) {
425		$rv = $setin->{$n} ? $a->{'opts'} : "";
426		}
427	elsif ($a->{'type'} == 9) {
428		local $found;
429		foreach my $l (&read_opts_file($a->{'opts'})) {
430			$found++ if ($l->[0] eq $setin->{$n});
431			}
432		$found || $skipfound || &error($text{'run_eopt'});
433		$rv = $setin->{$n};
434		}
435	elsif ($a->{'type'} == 10) {
436		if ($setin->{$n}) {
437			if ($setin->{$n."_filename"} =~ /([^\/\\]+$)/ && $1) {
438				$rv = &transname("$1");
439				}
440			else {
441				$rv = &transname();
442				}
443			&open_tempfile(TEMP, ">$rv");
444			&print_tempfile(TEMP, $setin->{$n});
445			&close_tempfile(TEMP);
446			chown($uinfo->[2], $uinfo->[3], $rv);
447			push(@unlink, $rv);
448			}
449		else {
450			$a->{'must'} && &error($text{'run_eupload'});
451			$rv = undef;
452			}
453		}
454	elsif ($a->{'type'} == 12 || $a->{'type'} == 13 || $a->{'type'} == 14) {
455		local @vals;
456		if ($a->{'type'} == 14) {
457			@vals = split(/\r?\n/, $setin->{$n});
458			}
459		else {
460			@vals = split(/\0/, $setin->{$n});
461			}
462		local @opts = &read_opts_file($a->{'opts'});
463		foreach my $v (@vals) {
464			local $found;
465			foreach my $l (@opts) {
466				$found++ if ($l->[0] eq $v);
467				}
468			$found || $skipfound || &error($text{'run_eopt'});
469			}
470		$rv = join(" ", @vals);
471		}
472	elsif ($a->{'type'} == 15) {
473		$rv = $setin->{$n."_year"}."-".
474		      $setin->{$n."_month"}."-".
475		      $setin->{$n."_day"};
476		}
477	elsif ($a->{'type'} == 16) {
478		$rv = $setin->{$n} ? 1 : 0;
479		}
480	if ($rv eq '' && $a->{'must'} && $a->{'type'} != 7) {
481		&error(&text('run_emust', $a->{'desc'}));
482		}
483	$ENV{$n} = $rv;
484	$env .= "$n='$rv'\n";
485	$export .= " $n";
486	if ($a->{'quote'}) {
487		$str =~ s/\$$n/"\$$n"/g;
488		$displaystr =~ s/\$$n/"$rv"/g;
489		}
490	else {
491		$displaystr =~ s/\$$n/$rv/g;
492		}
493	push(@vals, $rv);
494	}
495return ($env, $export, $str, $displaystr, \@vals);
496}
497
498# list_dbi_drivers()
499# Returns a list of DBI driver details, which are actually installed
500sub list_dbi_drivers
501{
502local @rv = ( { 'name' => 'MySQL',
503		'driver' => 'mysql',
504		'dbparam' => 'database' },
505	      { 'name' => 'PostgreSQL',
506		'driver' => 'Pg',
507		'dbparam' => 'dbname' },
508	    );
509@rv = grep { eval "use DBD::$_->{'driver'}"; !$@ } @rv;
510return @rv;
511}
512
513# list_servers()
514# Returns a list of servers that a command can run on
515sub list_servers
516{
517if (&foreign_installed("servers")) {
518	&foreign_require("servers", "servers-lib.pl");
519	@servers = grep { $_->{'user'} } &servers::list_servers();
520	if (@servers) {
521		return ( { 'id' => 0, 'desc' => $text{'edit_this'} }, @servers);
522		}
523	}
524return ( { 'id' => 0, 'desc' => $text{'edit_this'} } );
525}
526
527# execute_custom_command(&command, environment, exports, string, print-output)
528# Runs some command, and returns the bytes, output and timeout flag
529sub execute_custom_command
530{
531local ($cmd, $env, $export, $str, $print) = @_;
532&foreign_require("proc", "proc-lib.pl");
533
534&clean_environment() if ($cmd->{'clear'});
535local $got;
536local $outtemp = &transname();
537open(OUTTEMP, ">$outtemp");
538local $fh = $print ? STDOUT : *OUTTEMP;
539if ($cmd->{'su'}) {
540	local $temp = &transname();
541	&open_tempfile(TEMP, ">$temp");
542	&print_tempfile(TEMP, "#!/bin/sh\n");
543	&print_tempfile(TEMP, $env);
544	&print_tempfile(TEMP, "export $export\n") if ($export);
545	&print_tempfile(TEMP, "$str\n");
546	&close_tempfile(TEMP);
547	chmod(0755, $temp);
548	$got = &proc::safe_process_exec(
549			     &command_as_user($user, 1, $temp), 0, 0,
550			     $fh, undef, !$cmd->{'raw'} && !$cmd->{'format'}, 0,
551			     $cmd->{'timeout'});
552	unlink($temp);
553	}
554else {
555	$got = &proc::safe_process_exec(
556			     $str, $user_info[2], undef, $fh, undef,
557			     !$cmd->{'raw'} && !$cmd->{'format'}, 0,
558			     $cmd->{'timeout'});
559	}
560local $ex = $?;
561&reset_environment() if ($cmd->{'clear'});
562close(OUTTEMP);
563local $rv = &read_file_contents($outtemp);
564unlink($outtemp);
565return ($got, $rv, $proc::safe_process_exec_timeout ? 1 : 0, $ex);
566}
567
568# show_parameter_input(&arg, formno)
569# Returns HTML for a parameter input
570sub show_parameter_input
571{
572local ($a, $form) = @_;
573local $n = $a->{'name'};
574local $v = $a->{'opts'};
575if ($a->{'type'} != 9 && $a->{'type'} != 12 &&
576    $a->{'type'} != 13 && $a->{'type'} != 14) {
577	if ($v =~ /^"(.*)"$/ || $v =~ /^'(.*)'$/) {
578		# Quoted default
579		$v = $1;
580		}
581	elsif ($v =~ /^(.*)\s*\|$/ && $config{'params_cmd'}) {
582		# Command to run
583		$v = &backquote_command("$1 2>/dev/null </dev/null");
584		if ($a->{'type'} != 11) {
585			$v =~ s/[\r\n]+$//;
586			}
587		}
588	elsif ($v =~ /^\// && $config{'params_file'}) {
589		# File to read
590		$v = &read_file_contents($v);
591		if ($a->{'type'} != 11) {
592			$v =~ s/[\r\n]+$//;
593			}
594		}
595	}
596if ($a->{'type'} == 0) {
597	return &ui_textbox($n, $v, 30);
598	}
599elsif ($a->{'type'} == 1 || $a->{'type'} == 2) {
600	return &ui_user_textbox($n, $v, $form);
601	}
602elsif ($a->{'type'} == 3 || $a->{'type'} == 4) {
603	return &ui_group_textbox($n, $v, $form);
604	}
605elsif ($a->{'type'} == 5 || $a->{'type'} == 6) {
606	return &ui_textbox($n, $v, 30)." ".
607	       &file_chooser_button($n, $a->{'type'}-5, $form);
608	}
609elsif ($a->{'type'} == 7) {
610	return &ui_yesno_radio($n, $v =~ /true|yes|1/ ? 1 : 0);
611	}
612elsif ($a->{'type'} == 8) {
613	return &ui_password($n, $v, 30);
614	}
615elsif ($a->{'type'} == 9) {
616	return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ]);
617	}
618elsif ($a->{'type'} == 10) {
619	return &ui_upload($n, 30);
620	}
621elsif ($a->{'type'} == 11) {
622	return &ui_textarea($n, $v, 4, 30);
623	}
624elsif ($a->{'type'} == 12) {
625	return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ],
626			  5, 1);
627	}
628elsif ($a->{'type'} == 13) {
629	my @opts = &read_opts_file($a->{'opts'});
630	return &ui_select($n, undef, \@opts, scalar(@opts), 1);
631	}
632elsif ($a->{'type'} == 14) {
633	my @opts = &read_opts_file($a->{'opts'});
634	return &ui_multi_select($n, [ ], \@opts, 5);
635	}
636elsif ($a->{'type'} == 15) {
637	my ($year, $month, $day) = split(/\-/, $v);
638	return &ui_date_input($day, $month, $year,
639			      $n."_day", $n."_month", $n."_year")."&nbsp;".
640	       &date_chooser_button($n."_day", $n."_month", $n."_year");
641	}
642elsif ($a->{'type'} == 16) {
643	return &ui_submit($v || $a->{'name'}, $a->{'name'});
644	}
645else {
646	return "Unknown parameter type $a->{'type'}";
647	}
648}
649
650
6511;
652