1=head1 usermin-lib.pl
2
3Functions for configuring Usermin running on this system. Example usage :
4
5 foreign_require("usermin", "usermin-lib.pl");
6 @usermods = usermin::list_usermin_usermods();
7 push(@usermods, [ 'joe', '', 'mailbox changepass' ]);
8 usermin::save_usermin_usermods(\@usermods);
9
10=cut
11
12BEGIN { push(@INC, ".."); };
13use WebminCore;
14&init_config();
15%access = &get_module_acl();
16$access{'upgrade'} = 0 if (&is_readonly_mode());	# too hard to fake
17&foreign_require("webmin");
18&foreign_require("acl");
19%text = ( %webmin::text, %text );
20
21$usermin_miniserv_config = "$config{'usermin_dir'}/miniserv.conf";
22$usermin_config = "$config{'usermin_dir'}/config";
23
24$update_host = "www.webmin.com";
25$update_port = 80;
26$update_page = "/uupdates/uupdates.txt";
27
28$standard_usermin_dir = "/etc/usermin";
29$latest_rpm = "http://www.webmin.com/download/usermin-latest.noarch.rpm";
30$latest_tgz = "http://www.webmin.com/download/usermin-latest.tar.gz";
31
32$default_key_size = 2048;
33
34$cron_cmd = "$module_config_directory/update.pl";
35
36=head2 get_usermin_miniserv_config(&hash)
37
38Similar to the standard get_miniserv_config function, but this one fills in
39the given hash ref with the contents of the /etc/usermin/miniserv.conf file.
40
41=cut
42sub get_usermin_miniserv_config
43{
44&read_file($usermin_miniserv_config, \%usermin_miniserv_config_cache)
45	if (!%usermin_miniserv_config_cache);
46%{$_[0]} = %usermin_miniserv_config_cache;
47}
48
49=head2 put_usermin_miniserv_config(&hash)
50
51Writes out the Usermin miniserv configuration, based on the given hash ref.
52
53=cut
54sub put_usermin_miniserv_config
55{
56%usermin_miniserv_config_cache = %{$_[0]};
57&write_file($usermin_miniserv_config, \%usermin_miniserv_config_cache);
58}
59
60=head2 get_usermin_version
61
62Returns the version number of Usermin on this system.
63
64=cut
65sub get_usermin_version
66{
67my ($ui_format_dev) = @_;
68local %miniserv;
69&get_usermin_miniserv_config(\%miniserv);
70open(VERSION, "<$miniserv{'root'}/version");
71local $version = <VERSION>;
72close(VERSION);
73$version =~ s/\r|\n//g;
74# Format dev version nicely
75if ($ui_format_dev && length($version) == 13) {
76	return substr($version, 0, 5) . "." . substr($version, 5, 5 - 1) . "." . substr($version, 5 * 2 - 1);
77	}
78else {
79	return $version;
80	}
81}
82
83=head2 restart_usermin_miniserv
84
85Send a HUP signal to Usermin's miniserv, telling it to restart and re-read
86all configuration files.
87
88=cut
89sub restart_usermin_miniserv
90{
91return undef if (&is_readonly_mode());
92local($pid, %miniserv, $addr, $i);
93&get_usermin_miniserv_config(\%miniserv) || return;
94$miniserv{'inetd'} && return;
95open(PID, "<".$miniserv{'pidfile'}) || &error("Failed to open PID file");
96chop($pid = <PID>);
97close(PID);
98if (!$pid) { &error("Invalid PID file"); }
99return &kill_logged('HUP', $pid);
100}
101
102=head2 reload_usermin_miniserv
103
104Sends a USR1 signal to the miniserv process, telling it to re-read most
105configuration files.
106
107=cut
108sub reload_usermin_miniserv
109{
110return undef if (&is_readonly_mode());
111local %miniserv;
112&get_usermin_miniserv_config(\%miniserv) || return;
113$miniserv{'inetd'} && return;
114
115local($pid, $addr, $i);
116open(PID, "<".$miniserv{'pidfile'}) || &error("Failed to open PID file");
117chop($pid = <PID>);
118close(PID);
119if (!$pid) { &error("Invalid PID file"); }
120return &kill_logged('USR1', $pid);
121}
122
123=head2 get_usermin_config(&hash)
124
125Fills in the given hash ref with the contents of the global Usermin
126configuration file, typically at /etc/usermin/config.
127
128=cut
129sub get_usermin_config
130{
131&read_file($usermin_config, \%usermin_config_cache)
132	if (!%usermin_config_cache);
133%{$_[0]} = %usermin_config_cache;
134}
135
136=head2 put_usermin_config(&hash)
137
138Writes the given hash ref to the global Usermin configuration file.
139
140=cut
141sub put_usermin_config
142{
143%usermin_config_cache = %{$_[0]};
144&write_file($usermin_config, \%usermin_config_cache);
145}
146
147=head2 get_usermin_root_directory
148
149Returns the Usermin install root directory.
150
151=cut
152sub get_usermin_root_directory
153{
154my %miniserv;
155&get_usermin_miniserv_config(\%miniserv);
156return $miniserv{'root'};
157}
158
159=head2 list_themes
160
161Returns an array of all usermin themes. The format is the same as the
162webmin::list_themes function.
163
164=cut
165sub list_themes
166{
167local @rv;
168local %miniserv;
169&get_usermin_miniserv_config(\%miniserv);
170opendir(DIR, $miniserv{'root'});
171foreach $m (readdir(DIR)) {
172	next if ($m =~ /^\./);
173	local %tinfo = &get_usermin_theme_info($m);
174	next if (!%tinfo);
175	next if (!&check_usermin_os_support(\%tinfo));
176	push(@rv, \%tinfo);
177	}
178closedir(DIR);
179return @rv;
180}
181
182=head2 list_visible_themes([current-theme])
183
184Lists all themes the user should be able to use, possibly including their
185current theme if one is set.
186
187=cut
188sub list_visible_themes
189{
190my ($curr) = @_;
191my @rv;
192my %done;
193foreach my $theme (&list_themes()) {
194        my $iscurr = $curr && $theme->{'dir'} eq $curr;
195        next if (-l $root_directory."/".$theme->{'dir'} &&
196                 !$iscurr);
197        next if ($done{$theme->{'desc'}}++ && !$iscurr);
198        push(@rv, $theme);
199        }
200return @rv;
201}
202
203=head2 list_modules
204
205Returns a list of all usermin modules installed and supported on this system.
206Each is a hash ref in the same format as returned by Webmin's get_module_info
207function.
208
209=cut
210sub list_modules
211{
212local (@mlist, $m, %miniserv);
213&get_usermin_miniserv_config(\%miniserv);
214local %cats;
215&read_file_cached("$config{'usermin_dir'}/webmin.cats", \%cats);
216opendir(DIR, $miniserv{'root'});
217foreach $m (readdir(DIR)) {
218	local %minfo;
219	if ((%minfo = &get_usermin_module_info($m)) &&
220	    &check_usermin_os_support(\%minfo)) {
221		$minfo{'realcategory'} = $minfo{'category'};
222		$minfo{'category'} = $cats{$m} if (defined($cats{$m}));
223		push(@mlist, \%minfo);
224		}
225	}
226closedir(DIR);
227@mlist = sort { $a->{'desc'} cmp $b->{'desc'} } @mlist;
228return @mlist;
229}
230
231=head2 get_usermin_module_info(module, [noclone])
232
233Returns a hash contain details of a module, in the same format as
234Webmin's get_module_info function. Useful keys include :
235
236=item dir - The module's relative directory.
237
238=item desc - The human-readable title.
239
240=item category - Category the module is in, like login or apps.
241
242=item depends - Space-separated list of dependent modules.
243
244=item os_support - List of supported operating systems and versions.
245
246=cut
247sub get_usermin_module_info
248{
249return () if ($_[0] =~ /^\./);
250local (%rv, $clone, %miniserv, $o);
251&get_usermin_miniserv_config(\%miniserv);
252&read_file("$miniserv{'root'}/$_[0]/module.info", \%rv) || return ();
253$clone = -l "$miniserv{'root'}/$_[0]";
254foreach $o (@lang_order_list) {
255	$rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
256	}
257if ($clone && !$_[1] && $config_directory) {
258	$rv{'clone'} = $rv{'desc'};
259	&read_file("$config{'usermin_dir'}/$_[0]/clone", \%rv);
260	}
261$rv{'dir'} = $_[0];
262$rv{'realcategory'} = $rv{'category'};
263
264# Apply description overrides
265$rv{'realdesc'} = $rv{'desc'};
266local %descs;
267&read_file_cached("$config{'usermin_dir'}/webmin.descs", \%descs);
268if ($descs{$_[0]." ".$current_lang}) {
269	$rv{'desc'} = $descs{$_[0]." ".$current_lang};
270	}
271elsif ($descs{$_[0]}) {
272	$rv{'desc'} = $descs{$_[0]};
273	}
274
275return %rv;
276}
277
278=head2 get_usermin_theme_info(theme)
279
280Like get_usermin_module_info, but returns the details of a theme instead.
281This is basically the contents of its theme.info file.
282
283=cut
284sub get_usermin_theme_info
285{
286local (%tinfo, $o);
287local %miniserv;
288&get_usermin_miniserv_config(\%miniserv);
289&read_file("$miniserv{'root'}/$_[0]/theme.info", \%tinfo) || return ();
290foreach $o (@lang_order_list) {
291	$tinfo{"desc"} = $rv{"desc_$o"} if ($tinfo{"desc_$o"});
292	}
293$tinfo{'dir'} = $_[0];
294return %tinfo;
295}
296
297=head2 check_usermin_os_support(&minfo)
298
299Given a Usermin module information hash ref (as returned by
300get_usermin_module_info), checks if it is supported on this OS. Returns 1 if
301yes, 0 if no.
302
303=cut
304sub check_usermin_os_support
305{
306local $oss = $_[0]->{'os_support'};
307return 1 if (!$oss || $oss eq '*');
308local %uconfig;
309&get_usermin_config(\%uconfig);
310while(1) {
311	local ($os, $ver, $codes);
312	if ($oss =~ /^([^\/\s]+)\/([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
313		$os = $1; $ver = $2; $codes = $3; $oss = $4;
314		}
315	elsif ($oss =~ /^([^\/\s]+)\/([^\/\s]+)\s*(.*)$/) {
316		$os = $1; $ver = $2; $oss = $3;
317		}
318	elsif ($oss =~ /^([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
319		$os = $1; $codes = $2; $oss = $3;
320		}
321	elsif ($oss =~ /^\{([^\}]*)\}\s*(.*)$/) {
322		$codes = $1; $oss = $2;
323		}
324	elsif ($oss =~ /^(\S+)\s*(.*)$/) {
325		$os = $1; $oss = $2;
326		}
327	else { last; }
328	next if ($os && !($os eq $uconfig{'os_type'} ||
329		 $uconfig{'os_type'} =~ /^(\S+)-(\S+)$/ && $os eq "*-$2"));
330	next if ($ver && $ver ne $uconfig{'os_version'});
331	next if ($codes && !eval $codes);
332	return 1;
333	}
334return 0;
335}
336
337=head2 read_usermin_acl(&array, &array)
338
339Reads the acl file into the given hashes. The first maps user,module to
3401 where granted, which the second maps a user to an array ref of module dirs.
341
342=cut
343sub read_usermin_acl
344{
345local($user, $_, @mods);
346if (!%usermin_acl_hash_cache) {
347	open(ACL, "<".&usermin_acl_filename());
348	while(<ACL>) {
349		if (/^(\S+):\s*(.*)/) {
350			local(@mods);
351			$user = $1;
352			@mods = split(/\s+/, $2);
353			foreach $m (@mods) {
354				$usermin_acl_hash_cache{$user,$m}++;
355				}
356			$usermin_acl_array_cache{$user} = \@mods;
357			}
358		}
359	close(ACL);
360	}
361if ($_[0]) { %{$_[0]} = %usermin_acl_hash_cache; }
362if ($_[1]) { %{$_[1]} = %usermin_acl_array_cache; }
363}
364
365=head2 usermin_acl_filename
366
367Returns the file containing the webmin ACL.
368
369=cut
370sub usermin_acl_filename
371{
372return "$config{'usermin_dir'}/webmin.acl";
373}
374
375=head2 save_usermin_acl(user, &modules)
376
377Updates the list of available modules in Usermin.
378
379=cut
380sub save_usermin_acl
381{
382&open_tempfile(ACL, ">".&usermin_acl_filename());
383&print_tempfile(ACL, $_[0],": ",join(" ", @{$_[1]}),"\n");
384&close_tempfile(ACL);
385}
386
387=head2 install_usermin_module(file, unlink, nodeps)
388
389Installs a usermin module or theme, and returns either an error message
390or references to three arrays for descriptions, directories and sizes.
391On success or failure, the file is deleted if the unlink parameter is set.
392
393=cut
394sub install_usermin_module
395{
396local ($file, $need_unlink, $nodeps) = @_;
397local (@mdescs, @mdirs, @msizes);
398if (&is_readonly_mode()) {
399	return "Module installs are not allowed in readonly mode";
400	}
401
402# Uncompress the module file if needed
403open(MFILE, "<".$file);
404read(MFILE, $two, 2);
405close(MFILE);
406if ($two eq "\037\235") {
407	if (!&has_command("uncompress")) {
408		unlink($file) if ($need_unlink);
409		return &text('install_ecomp', "<tt>uncompress</tt>");
410		}
411	local $temp = $file =~ /\/([^\/]+)\.Z/i ? &transname("$1")
412						: &transname();
413	local $out = `uncompress -c "$file" 2>&1 >$temp`;
414	unlink($file) if ($need_unlink);
415	if ($?) {
416		unlink($temp);
417		return &text('install_ecomp2', $out);
418		}
419	$file = $temp;
420	$need_unlink = 1;
421	}
422elsif ($two eq "\037\213") {
423	if (!&has_command("gunzip")) {
424		unlink($file) if ($need_unlink);
425		return &text('install_egzip', "<tt>gunzip</tt>");
426		}
427	local $temp = $file =~ /\/([^\/]+)\.gz/i ? &transname("$1")
428						 : &transname();
429	local $out = `gunzip -c "$file" 2>&1 >$temp`;
430	unlink($file) if ($need_unlink);
431	if ($?) {
432		unlink($temp);
433		return &text('install_egzip2', $out);
434		}
435	$file = $temp;
436	$need_unlink = 1;
437	}
438
439local %miniserv;
440&get_usermin_miniserv_config(\%miniserv);
441
442# Check if this is an RPM usermin module or theme
443local ($type, $redirect_to);
444open(TYPE, "<../install-type");
445chop($type = <TYPE>);
446close(TYPE);
447if ($type eq 'rpm' && $file =~ /\.rpm$/i &&
448    ($out = `rpm -qp $file 2>/dev/null`)) {
449	# Looks like an RPM of some kind, hopefully an RPM usermin module
450	# or theme
451	local ($out, %minfo, %tinfo);
452	if ($out !~ /^(wbm|wbt)-([^\s\-]+)/) {
453		unlink($file) if ($need_unlink);
454		return $text{'install_erpm'};
455		}
456	$redirect_to = $name = $2;
457	$out = &backquote_logged("rpm -U \"$file\" 2>&1");
458	if ($?) {
459		unlink($file) if ($need_unlink);
460		return &text('install_eirpm', "<tt>$out</tt>");
461		}
462
463	$mdirs[0] = "$miniserv{'root'}/$name";
464	if (%minfo = &get_usermin_module_info($name)) {
465		# Get the new module info
466		$mdescs[0] = $minfo{'desc'};
467		$msizes[0] = &disk_usage_kb($mdirs[0]);
468
469		# Update the ACL for the usermin user
470		local %acl;
471		&read_usermin_acl(undef, \%acl);
472		&open_tempfile(ACL, "> ".&usermin_acl_filename());
473		foreach $u (keys %acl) {
474			local @mods = @{$acl{$u}};
475			if ($u eq 'user') {
476				push(@mods, $name);
477				@mods = &unique(@mods);
478				}
479			&print_tempfile(ACL, "$u: ",join(' ', @mods),"\n");
480			}
481		&close_tempfile(ACL);
482		&webmin_log("install", undef, $name,
483			    { 'desc' => $mdescs[0] });
484		}
485	elsif (%tinfo = &get_usermin_theme_info($name)) {
486		# Get the theme info
487		$mdescs[0] = $tinfo{'desc'};
488		$msizes[0] = &disk_usage_kb($mdirs[0]);
489		&webmin_log("tinstall", undef, $name,
490			    { 'desc' => $mdescs[0] });
491		}
492	else {
493		unlink($file) if ($need_unlink);
494		return $text{'install_eneither'};
495		}
496	}
497else {
498	# Check if this is a valid module (a tar file of multiple module or
499	# theme directories)
500	local (%mods, %hasfile);
501	local $tar = `tar tf "$file" 2>&1`;
502	if ($?) {
503		unlink($file) if ($need_unlink);
504		return &text('install_etar', $tar);
505		}
506	foreach $f (split(/\n/, $tar)) {
507		if ($f =~ /^\.\/([^\/]+)\/(.*)$/ || $f =~ /^([^\/]+)\/(.*)$/) {
508			$redirect_to = $1 if (!$redirect_to);
509			$mods{$1}++;
510			$hasfile{$1,$2}++;
511			}
512		}
513	foreach $m (keys %mods) {
514		if (!$hasfile{$m,"module.info"} && !$hasfile{$m,"theme.info"}) {
515			unlink($file) if ($need_unlink);
516			return &text('install_einfo', "<tt>$m</tt>");
517			}
518		}
519	if (!%mods) {
520		unlink($file) if ($need_unlink);
521		return $text{'install_enone'};
522		}
523
524	# Get the module.info files to check dependencies
525	local $ver = &get_usermin_version();
526	local $tmpdir = &transname();
527	mkdir($tmpdir, 0700);
528	local $err;
529	local @realmods;
530	foreach $m (keys %mods) {
531		next if (!$hasfile{$m,"module.info"});
532		push(@realmods, $m);
533		local %minfo;
534		system("cd $tmpdir ; tar xf \"$file\" $m/module.info ./$m/module.info >/dev/null 2>&1");
535		if (!&read_file("$tmpdir/$m/module.info", \%minfo)) {
536			$err = &text('install_einfo', "<tt>$m</tt>");
537			}
538		elsif (!&check_usermin_os_support(\%minfo)) {
539			$err = &text('install_eos', "<tt>$m</tt>",
540				     $gconfig{'real_os_type'},
541				     $gconfig{'real_os_version'});
542			}
543		elsif (!$minfo{'usermin'}) {
544			$err = &text('install_eusermin', "<tt>$m</tt>");
545			}
546		elsif (!$nodeps) {
547			local $deps = $minfo{'usermin_depends'} ||
548				      $minfo{'depends'};
549			foreach $dep (split(/\s+/, $minfo{'depends'})) {
550				if ($dep =~ /^[0-9\.]+$/) {
551					if ($dep > $ver) {
552						$err = &text('install_ever',
553							"<tt>$m</tt>",
554							"<tt>$dep</tt>");
555						}
556					}
557				elsif (!-r "$miniserv{'root'}/$dep/module.info"
558				       && !$mods{$dep}) {
559					$err = &text('install_edep',
560					        "<tt>$m</tt>", "<tt>$dep</tt>");
561					}
562				}
563			foreach $dep (split(/\s+/, $minfo{'perldepends'})) {
564				eval "use $dep";
565				if ($@) {
566					$err = &text('install_eperldep',
567					     "<tt>$m</tt>", "<tt>$dep</tt>",
568					     "/cpan/download.cgi?source=3&cpan=$dep");
569					}
570				}
571			}
572		last if ($err);
573		}
574	system("rm -rf $tmpdir >/dev/null 2>&1");
575	if ($err) {
576		unlink($file) if ($need_unlink);
577		return $err;
578		}
579
580	# Delete modules or themes being replaced
581	foreach $m (@realmods) {
582		system("rm -rf '$miniserv{'root'}/$m' 2>&1 >/dev/null") if ($m ne 'webmin');
583		}
584
585	# Extract all the modules and update perl path and ownership
586	local $out = `cd $miniserv{'root'} ; tar xf "$file" 2>&1 >/dev/null`;
587	if ($?) {
588		unlink($file) if ($need_unlink);
589		return &text('install_eextract', $out);
590		}
591	if ($need_unlink) { unlink($file); }
592	local $perl;
593	open(PERL, "<$miniserv{'root'}/miniserv.pl");
594	<PERL> =~ /^#!(\S+)/; $perl = $1;
595	close(PERL);
596	local @st = stat($0);
597	foreach $moddir (keys %mods) {
598		local $pwd = "$miniserv{'root'}/$moddir";
599		if ($hasfile{$moddir,"module.info"}) {
600			local %minfo = &get_usermin_module_info($moddir);
601			push(@mdescs, $minfo{'desc'});
602			push(@mdirs, $pwd);
603			push(@msizes, &disk_usage_kb($pwd));
604			&webmin_log("install", undef, $moddir,
605				    { 'desc' => $minfo{'desc'} });
606			}
607		else {
608			local %tinfo = &get_usermin_theme_info($moddir);
609			&read_file("theme.info", \%tinfo);
610			push(@mdescs, $tinfo{'desc'});
611			push(@mdirs, $pwd);
612			push(@msizes, &disk_usage_kb($pwd));
613			&webmin_log("tinstall", undef, $moddir,
614				    { 'desc' => $tinfo{'desc'} });
615			}
616		system("(find $pwd -name '*.cgi' ; find $pwd -name '*.pl') 2>/dev/null | $perl $miniserv{'root'}/perlpath.pl $perl -");
617		system("chown -R $st[4]:$st[5] $pwd");
618		}
619
620	# Copy appropriate config file from modules to /etc/webmin
621	local %ugconfig;
622	&get_usermin_config(\%ugconfig);
623	system("$perl $miniserv{'root'}/copyconfig.pl '$ugconfig{'os_type'}/$ugconfig{'real_os_type'}' '$ugconfig{'os_version'}/$ugconfig{'real_os_version'}' $miniserv{'root'} $config{'usermin_dir'} ".join(' ', @realmods));
624
625	# Update ACL for this user so they can access the new modules
626	local %acl;
627	&read_usermin_acl(undef, \%acl);
628	&open_tempfile(ACL, "> ".&usermin_acl_filename());
629	foreach $u (keys %acl) {
630		local @mods = @{$acl{$u}};
631		if ($u eq 'user') {
632			push(@mods, @realmods);
633			@mods = &unique(@mods);
634			}
635		&print_tempfile(ACL, "$u: ",join(' ', @mods),"\n");
636		}
637	&close_tempfile(ACL);
638	}
639&flush_modules_cache();
640
641return [ \@mdescs, \@mdirs, \@msizes ];
642}
643
644=head2 list_usermin_usermods
645
646Returns the list of additional module restrictions for usermin.
647This is a list of array refs, each element of which contains a username,
648a flag and an array ref of module names. The flag can be one of :
649
650=item + - Add the modules to the list available to this user.
651
652=item - - Take the modules away from this user.
653
654=item blank - Assign the modules to the list for this user.
655
656=cut
657sub list_usermin_usermods
658{
659local @rv;
660open(USERMODS, "<$config{'usermin_dir'}/usermin.mods");
661while(<USERMODS>) {
662	if (/^([^:]+):(\+|-|):(.*)/) {
663		push(@rv, [ $1, $2, [ split(/\s+/, $3) ] ]);
664		}
665	}
666close(USERMODS);
667return @rv;
668}
669
670=head2 save_usermin_usermods(&usermods)
671
672Saves the list of additional module restrictions. This must be an array ref
673in the same format as returned by list_usermin_usermods.
674
675=cut
676sub save_usermin_usermods
677{
678&open_tempfile(USERMODS, ">$config{'usermin_dir'}/usermin.mods");
679foreach $u (@{$_[0]}) {
680	&print_tempfile(USERMODS,
681		join(":", $u->[0], $u->[1], join(" ", @{$u->[2]})),"\n");
682	}
683&close_tempfile(USERMODS);
684}
685
686=head2 get_usermin_miniserv_users
687
688Returns a list of Usermin users from miniserv.users. In normal use, there
689is only one, as all authentication is done using Unix users.
690
691=cut
692sub get_usermin_miniserv_users
693{
694local %miniserv;
695&get_usermin_miniserv_config(\%miniserv);
696local @rv;
697open(USERS, "<".$miniserv{'userfile'});
698while(<USERS>) {
699	s/\r|\n//g;
700	local @u = split(/:/, $_);
701	push(@rv, { 'name' => $u[0],
702		    'pass' => $u[1],
703		    'sync' => $u[2],
704		    'cert' => $u[3],
705		    'allow' => $u[4] });
706	}
707close(USERS);
708return @rv;
709}
710
711=head2 save_usermin_miniserv_users(&user, ...)
712
713Updats the list of Usermin miniserv users, each of which is a hash ref
714in the format returned by get_usermin_miniserv_users.
715
716=cut
717sub save_usermin_miniserv_users
718{
719local %miniserv;
720&get_usermin_miniserv_config(\%miniserv);
721&open_tempfile(USERS, ">$miniserv{'userfile'}");
722local $u;
723foreach $u (@_) {
724	&print_tempfile(USERS,
725		join(":", $u->{'name'}, $u->{'pass'}, $u->{'sync'},
726			  $u->{'cert'}, $u->{'allow'}),"\n");
727	}
728&close_tempfile(USERS);
729}
730
731=head2 can_use_module(module)
732
733Returns 1 if the current Webmin user can use some function of this module.
734
735=cut
736sub can_use_module
737{
738return 1 if ($access{'mods'} eq '*');
739local @mods = split(/\s+/, $access{'mods'});
740return &indexof($_[0], @mods) >= 0;
741}
742
743=head2 get_usermin_base_version
744
745Gets the usermin version, rounded to the nearest .01
746
747=cut
748sub get_usermin_base_version
749{
750return &base_version(&get_usermin_version());
751}
752
753=head2 base_version
754
755Rounds a version number to the nearest .01
756
757=cut
758sub base_version
759{
760return sprintf("%.2f0", $_[0]);
761}
762
763=head2 find_cron_job(\@jobs)
764
765Finds the cron job for Usermin updates, given an array ref of cron jobs
766as returned by cron::list_cron_jobs.
767
768=cut
769sub find_cron_job
770{
771local ($job) = grep { $_->{'user'} eq 'root' &&
772		      $_->{'command'} eq $cron_cmd } @{$_[0]};
773return $job;
774}
775
776=head2 delete_usermin_module(module, [delete-acls])
777
778Deletes some usermin module, clone or theme, and return a description of
779the thing deleted.
780
781=cut
782sub delete_usermin_module
783{
784local $m = $_[0];
785return undef if (!$m);
786local %minfo = &get_usermin_module_info($m);
787%minfo = &get_usermin_theme_info($m) if (!%minfo);
788return undef if (!%minfo);
789local ($mdesc, @aclrm);
790@aclrm = ( $m ) if ($_[1]);
791local %miniserv;
792&get_usermin_miniserv_config(\%miniserv);
793local %ugconfig;
794&get_usermin_config(\%uconfig);
795local $mdir = "$miniserv{'root'}/$m";
796local $cdir = "$config{'usermin_dir'}/$m";
797if ($minfo{'clone'}) {
798	# Deleting a clone
799	local %cinfo;
800	&read_file("$config{'usermin_dir'}/$m/clone", \%cinfo);
801	&unlink_logged($mdir);
802	&system_logged("rm -rf ".quotemeta($cdir));
803	if ($ugconfig{'theme'}) {
804		&unlink_logged("$miniserv{'root'}/$ugconfig{'theme'}/$m");
805		}
806	$mdesc = &text('delete_desc1', $minfo{'desc'}, $minfo{'clone'});
807	}
808else {
809	# Delete any clones of this module
810	local @clones;
811	local @mst = stat($mdir);
812	opendir(DIR, $miniserv{'root'});
813	local $l;
814	foreach $l (readdir(DIR)) {
815		@lst = stat("$miniserv{'root'}/$l");
816		if (-l "$miniserv{'root'}/$l" && $lst[1] == $mst[1]) {
817			&unlink_logged("$miniserv{'root'}/$l");
818			&system_logged("rm -rf $config{'usermin_dir'}/$l");
819			push(@clones, $l);
820			}
821		}
822	closedir(DIR);
823
824	open(TYPE, "<$mdir/install-type");
825	chop($type = <TYPE>);
826	close(TYPE);
827
828	# Deleting the real module
829	local $size = &disk_usage_kb($mdir);
830	$mdesc = &text('delete_desc2', "<b>$minfo{'desc'}</b>",
831			   "<tt>$mdir</tt>", $size);
832	if ($type eq 'rpm') {
833		# This module was installed from an RPM .. rpm -e it
834		&system_logged("rpm -e ubm-$m");
835		}
836	else {
837		# Module was installed from a .wbm file .. just rm it
838		&system_logged("rm -rf ".quotemeta($mdir));
839		}
840	}
841
842&webmin_log("delete", undef, $m, { 'desc' => $minfo{'desc'} });
843return $mdesc;
844}
845
846=head2 flush_modules_cache
847
848Forces a rebuild of the Usermin module cache.
849
850=cut
851sub flush_modules_cache
852{
853&unlink_file("$config{'usermin_dir'}/module.infos.cache");
854}
855
856=head2 stop_usermin
857
858Kills the running Usermin server process, returning undef on success or an
859error message on failure.
860
861=cut
862sub stop_usermin
863{
864local %miniserv;
865&get_usermin_miniserv_config(\%miniserv);
866local $pid;
867if (open(PID, "<".$miniserv{'pidfile'}) && ($pid = int(<PID>))) {
868	&kill_logged('TERM', $pid) || return &text('stop_ekill', $!);
869	close(PID);
870	}
871else {
872	return $text{'stop_efile'};
873	}
874return undef;
875}
876
877=head2 start_usermin
878
879Starts the Usermin server process. Return value is always undef.
880
881=cut
882sub start_usermin
883{
884&system_logged("$config{'usermin_dir'}/start >/dev/null 2>&1 </dev/null");
885return undef;
886}
887
888=head2 get_install_type
889
890Returns the package type Usermin was installed form (rpm, deb, solaris-pkg
891or undef for tar.gz).
892
893=cut
894sub get_install_type
895{
896local (%miniserv, $mode);
897&get_usermin_miniserv_config(\%miniserv);
898if (open(MODE, "<$miniserv{'root'}/install-type")) {
899	chop($mode = <MODE>);
900	close(MODE);
901	}
902else {
903	if ($miniserv{'root'} eq "/usr/libexec/usermin") {
904		$mode = "rpm";
905		}
906	elsif ($miniserv{'root'} eq "/usr/share/usermin") {
907		$mode = "deb";
908		}
909	else {
910		$mode = undef;
911		}
912	}
913return $mode;
914}
915
916=head2 switch_to_usermin_user(username)
917
918Returns a set-cookie header and redirect URL for auto-logging into Usermin
919as some user.
920
921=cut
922sub switch_to_usermin_user
923{
924my ($user) = @_;
925
926# Stop Usermin first, so that the DBM can be safely written
927my %miniserv;
928&get_usermin_miniserv_config(\%miniserv);
929my $stopped;
930if (&check_pid_file($miniserv{'pidfile'})) {
931	&stop_usermin();
932	$stopped = 1;
933	}
934
935# Generate a session ID and set it in the DB
936&acl::open_session_db(\%miniserv);
937&seed_random();
938my $now = time();
939my $sid = int(rand()*$now);
940$acl::sessiondb{$sid} = "$user $now $ENV{'REMOTE_ADDR'}";
941dbmclose(%acl::sessiondb);
942if ($stopped) {
943	&start_usermin();
944	}
945&reload_usermin_miniserv();
946eval "use Net::SSLeay";
947if ($@) {
948	$miniserv{'ssl'} = 0;
949	}
950my $ssl = $miniserv{'ssl'} || $miniserv{'inetd_ssl'};
951my $sec = $ssl ? "; secure" : "";
952my $sidname = $miniserv{'sidname'} || 'sid';
953my $cookie = "$sidname=$sid; path=/$sec";
954
955# Work out redirect host
956my @sockets = &webmin::get_miniserv_sockets(\%miniserv);
957my ($host, $port);
958if ($config{'host'}) {
959	# Specific hostname set
960	$host = $config{'host'};
961	}
962else {
963	if ($sockets[0]->[0] ne "*") {
964		# Listening on special IP
965		$host = $sockets[0]->[0];
966		$port = $sockets[0]->[1] if ($sockets[0]->[1] ne '*');
967		}
968	else {
969		# Use same hostname as this server
970		$host = $ENV{'HTTP_HOST'};
971		$host =~ s/:.*//;
972		}
973	}
974$port ||= $config{'port'} || $miniserv{'port'};
975
976return ($cookie, ($ssl ? "https://" : "http://").$host.":".$port."/");
977}
978
979=head2 get_usermin_email_url([module], [cgi], [force-default], [force-host])
980
981Returns the base URL for accessing Usermin on this system, for use in emails.
982
983=cut
984sub get_usermin_email_url
985{
986my ($mod, $cgi, $def, $forcehost) = @_;
987
988# Work out the base URL
989my $url;
990if (!$def && $config{'usermin_email_url'}) {
991	$url = $config{'usermin_email_url'};
992	}
993else {
994	my %miniserv;
995	&get_usermin_miniserv_config(\%miniserv);
996	my $proto = $miniserv{'ssl'} ? 'https' : 'http';
997	my $port = $miniserv{'port'};
998	my $host = $forcehost || &get_system_hostname();
999	my $defport = $proto eq 'https' ? 443 : 80;
1000	$url = $proto."://".$host.($port == $defport ? "" : ":".$port);
1001	}
1002
1003# Append module if needed
1004$url =~ s/\/$//;
1005if ($mod && $cgi) {
1006	$url .= "/".$mod."/".$cgi;
1007	}
1008elsif ($mod) {
1009	$url .= "/".$mod."/";
1010	}
1011elsif ($cgi) {
1012	$url .= "/".$cgi;
1013	}
1014return $url;
1015}
1016
1017=head2 create_cron_wrapper(wrapper-path, module, script)
1018
1019Creates a wrapper script which calls a script in some module's directory
1020with the proper usermin environment variables set.
1021
1022The parameters are :
1023
1024=item wrapper-path - Full path to the wrapper to create, like /etc/usermin/yourmodule/foo.pl
1025
1026=item module - Module containing the real script to call.
1027
1028=item script - Program within that module for the wrapper to run.
1029
1030=cut
1031sub create_cron_wrapper
1032{
1033my ($wrapper, $mod, $script) = @_;
1034my $perl_path = &get_perl_path();
1035&open_tempfile(CMD, ">$wrapper");
1036&print_tempfile(CMD, <<EOF
1037#!$perl_path
1038open(CONF, "<$usermin_miniserv_config") || die "Failed to open $usermin_miniserv_config : \$!";
1039while(<CONF>) {
1040        \$root = \$1 if (/^root=(.*)/);
1041        }
1042close(CONF);
1043\$root || die "No root= line found in $usermin_miniserv_config";
1044\$ENV{'PERLLIB'} = "\$root";
1045\$ENV{'WEBMIN_CONFIG'} = "$config{'usermin_dir'}";
1046\$ENV{'WEBMIN_VAR'} = "$ENV{'WEBMIN_VAR'}";
1047delete(\$ENV{'MINISERV_CONFIG'});
1048EOF
1049	);
1050if ($mod) {
1051	&print_tempfile(CMD, "chdir(\"\$root/$mod\");\n");
1052	&print_tempfile(CMD, "exec(\"\$root/$mod/$script\", \@ARGV) || die \"Failed to run \$root/$mod/$script : \$!\";\n");
1053	}
1054else {
1055	&print_tempfile(CMD, "chdir(\"\$root\");\n");
1056	&print_tempfile(CMD, "exec(\"\$root/$script\", \@ARGV) || die \"Failed to run \$root/$script : \$!\";\n");
1057	}
1058&close_tempfile(CMD);
1059chmod(0755, $wrapper);
1060}
1061
10621;
1063
1064