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