Handle hostnames with upper-case letters
[webmin.git] / rbac / rbac-lib.pl
1 #, $in{'mode'} == 1 Functions for parsing the various RBAC configuration files
2
3 BEGIN { push(@INC, ".."); };
4 use WebminCore;
5 &init_config();
6 %access = &get_module_acl();
7
8 ####################### functions for users #######################
9
10 # list_user_attrs()
11 # Returns a list of user attribute objects
12 sub list_user_attrs
13 {
14 if (!scalar(@list_user_attrs_cache)) {
15         @list_user_attrs_cache = ( );
16         local $lnum = 0;
17         open(ATTR, $config{'user_attr'});
18         while(<ATTR>) {
19                 s/\r|\n//g;
20                 s/#.*$//;
21                 local @w = split(/:/, $_, -1);
22                 if (@w == 5) {
23                         local $attr = { 'user' => $w[0],
24                                         'qualifier' => $w[1],
25                                         'res1' => $w[2],
26                                         'res2' => $w[3],
27                                         'type' => 'user',
28                                         'line' => $lnum,
29                                         'index' => scalar(@list_user_attrs_cache) };
30                         local $a;
31                         foreach $a (split(/;/, $w[4])) {
32                                 local ($an, $av) = split(/=/, $a, 2);
33                                 $attr->{'attr'}->{$an} = $av;
34                                 }
35                         push(@list_user_attrs_cache, $attr);
36                         }
37                 $lnum++;
38                 }
39         close(ATTR);
40         }
41 return \@list_user_attrs_cache;
42 }
43
44 # create_user_attr(&attr)
45 # Add a new user attribute
46 sub create_user_attr
47 {
48 local ($attr) = @_;
49 &list_user_attrs();     # init cache
50 local $lref = &read_file_lines($config{'user_attr'});
51 $attr->{'line'} = scalar(@$lref);
52 push(@$lref, &user_attr_line($attr));
53 $attr->{'index'} = scalar(@list_user_attrs_cache);
54 push(@list_user_attrs_cache, $attr);
55 &flush_file_lines();
56 }
57
58 # modify_user_attr(&attr)
59 # Updates an existing user attribute in the config file
60 sub modify_user_attr
61 {
62 local ($attr) = @_;
63 local $lref = &read_file_lines($config{'user_attr'});
64 $lref->[$attr->{'line'}] = &user_attr_line($attr);
65 &flush_file_lines();
66 }
67
68 # delete_user_attr(&attr)
69 # Removes one user attribute entry
70 sub delete_user_attr
71 {
72 local ($attr) = @_;
73 local $lref = &read_file_lines($config{'user_attr'});
74 splice(@$lref, $attr->{'line'}, 1);
75 splice(@list_user_attrs_cache, $attr->{'index'}, 1);
76 local $c;
77 foreach $c (@list_user_attrs_cache) {
78         $c->{'line'}-- if ($c->{'line'} > $attr->{'line'});
79         $c->{'index'}-- if ($c->{'index'} > $attr->{'index'});
80         }
81 &flush_file_lines();
82 }
83
84 # user_attr_line(&attr)
85 # Returns the text for a user attribute line
86 sub user_attr_line
87 {
88 local ($attr) = @_;
89 local $rv = $attr->{'user'}.":".$attr->{'qualifier'}.":".$attr->{'res1'}.":".
90             $attr->{'res2'}.":";
91 $rv .= join(";", map { $_."=".$attr->{'attr'}->{$_} }
92                      keys %{$attr->{'attr'}});
93 return $rv;
94 }
95
96 # attr_input(name, value, [type], [acl-restrict])
97 # Returns HTML for selecting one or more user attrs
98 sub attr_input
99 {
100 local ($name, $value, $type, $restrict) = @_;
101 local @values = split(/,+/, $value);
102 local $users = &list_user_attrs();
103 local ($u, @sel);
104 foreach $u (@$users) {
105         local $utype = $u->{'attr'}->{'type'} || "normal";
106         if ((!$type || $utype eq $type) &&
107             (!$restrict || &can_assign_role($u))) {
108                 push(@sel, [ $u->{'user'} ]);
109                 }
110         }
111 if (@sel) {
112         return &ui_select($name, \@values, \@sel, 5, 1, 1);
113         }
114 else {
115         return $text{'attr_none'.$type};
116         }
117 }
118
119 # attr_parse(name)
120 # Returns a comma-separated list of values from an attrs field
121 sub attr_parse
122 {
123 return join(",", split(/\0/, $in{$_[0]}));
124 }
125
126 # all_recursive_roles(username)
127 # Returns all roles and sub-roles for some user
128 sub all_recursive_roles
129 {
130 local $users = &list_user_attrs();
131 local ($user) = grep { $_->{'user'} eq $_[0] } @$users;
132 local (@rv, $r);
133 foreach $r (split(/,/, $user->{'attr'}->{'roles'})) {
134         push(@rv, $r, &all_recursive_roles($r));
135         }
136 return @rv;
137 }
138
139 ####################### functions for profiles #######################
140
141 # list_prof_attrs()
142 # Returns a list of all profiles
143 sub list_prof_attrs
144 {
145 if (!scalar(@list_prof_attrs_cache)) {
146         @list_prof_attrs_cache = ( );
147         local $lnum = 0;
148         open(ATTR, $config{'prof_attr'});
149         while(<ATTR>) {
150                 s/\r|\n//g;
151                 s/#.*$//;
152                 local @w = split(/:/, $_, -1);
153                 if (@w == 5) {
154                         local $attr = { 'name' => $w[0],
155                                         'res1' => $w[1],
156                                         'res2' => $w[2],
157                                         'desc' => $w[3],
158                                         'line' => $lnum,
159                                         'type' => 'prof',
160                                         'index' => scalar(@list_prof_attrs_cache) };
161                         local $a;
162                         foreach $a (split(/;/, $w[4])) {
163                                 local ($an, $av) = split(/=/, $a, 2);
164                                 $attr->{'attr'}->{$an} = $av;
165                                 }
166                         push(@list_prof_attrs_cache, $attr);
167                         }
168                 $lnum++;
169                 }
170         close(ATTR);
171         $lnum++;
172         }
173 return \@list_prof_attrs_cache;
174 }
175
176 # create_prof_attr(&attr)
177 # Add a new profile
178 sub create_prof_attr
179 {
180 local ($attr) = @_;
181 &list_prof_attrs();     # init cache
182 local $lref = &read_file_lines($config{'prof_attr'});
183 $attr->{'line'} = scalar(@$lref);
184 push(@$lref, &prof_attr_line($attr));
185 $attr->{'index'} = scalar(@list_prof_attrs_cache);
186 push(@list_prof_attrs_cache, $attr);
187 &flush_file_lines();
188 }
189
190 # modify_prof_attr(&attr)
191 # Updates an existing profile in the config file
192 sub modify_prof_attr
193 {
194 local ($attr) = @_;
195 local $lref = &read_file_lines($config{'prof_attr'});
196 $lref->[$attr->{'line'}] = &prof_attr_line($attr);
197 &flush_file_lines();
198 }
199
200 # delete_prof_attr(&attr)
201 # Removes one profile
202 sub delete_prof_attr
203 {
204 local ($attr) = @_;
205 local $lref = &read_file_lines($config{'prof_attr'});
206 splice(@$lref, $attr->{'line'}, 1);
207 splice(@list_prof_attrs_cache, $attr->{'index'}, 1);
208 local $c;
209 foreach $c (@list_prof_attrs_cache) {
210         $c->{'line'}-- if ($c->{'line'} > $attr->{'line'});
211         $c->{'index'}-- if ($c->{'index'} > $attr->{'index'});
212         }
213 &flush_file_lines();
214 }
215
216 # prof_attr_line(&attr)
217 # Returns the text for a prof attribute line
218 sub prof_attr_line
219 {
220 local ($attr) = @_;
221 local $rv = $attr->{'name'}.":".$attr->{'res1'}.":".$attr->{'res2'}.":".
222             $attr->{'desc'}.":";
223 $rv .= join(";", map { $_."=".$attr->{'attr'}->{$_} }
224                      keys %{$attr->{'attr'}});
225 return $rv;
226 }
227
228 # profiles_input(name, comma-sep-value, acl-restrict)
229 # Returns HTML for a field for selecting multiple profiles
230 sub profiles_input
231 {
232 local ($name, $value, $restrict) = @_;
233 local @values = split(/,/, $value);
234 local $profs = &list_prof_attrs();
235 local @canprofs = $restrict ? grep { &can_assign_profile($_) } @$profs
236                             : @$profs;
237 if (@canprofs) {
238         return &ui_select($name, \@values,
239                           [ map { [ $_->{'name'}, "$_->{'name'} ($_->{'desc'})" ] }
240                                 sort { $a->{'name'} cmp $b->{'name'} }
241                                 @canprofs ], 10, 1, 1);
242         }
243 else {
244         return $text{'prof_none'};
245         }
246 }
247
248 # profiles_parse(name)
249 # Returns a comma-separated list of values from a profiles field
250 sub profiles_parse
251 {
252 return join(",", split(/\0/, $in{$_[0]}));
253 }
254
255 # all_recursive_profs(username|profilename)
256 # Returns all profiles and sub-profiles for some user
257 sub all_recursive_profs
258 {
259 local $users = &list_user_attrs();
260 local ($user) = grep { $_->{'user'} eq $_[0] } @$users;
261 local $profs = &list_prof_attrs();
262 local ($prof) = grep { $_->{'name'} eq $_[0] } @$profs;
263 local (@rv, $r);
264 if ($user) {
265         # Return all profiles from roles, direct profiles and sub-profiles
266         foreach $r (split(/,/, $user->{'attr'}->{'roles'})) {
267                 push(@rv, &all_recursive_profs($r));
268                 }
269         foreach $r (split(/,/, $user->{'attr'}->{'profiles'})) {
270                 push(@rv, $r, &all_recursive_profs($r));
271                 }
272         }
273 elsif ($prof) {
274         # Return all sub-profiles
275         foreach $r (split(/,/, $prof->{'attr'}->{'profs'})) {
276                 push(@rv, &all_recursive_profs($r));
277                 }
278         }
279 return @rv;
280 }
281
282 ####################### functions for authorizations #######################
283
284 # list_auth_attrs()
285 # Returns a user of all authorizations
286 sub list_auth_attrs
287 {
288 if (!scalar(@list_auth_attrs_cache)) {
289         @list_auth_attrs_cache = ( );
290         local $lnum = 0;
291         open(ATTR, $config{'auth_attr'});
292         while(<ATTR>) {
293                 s/\r|\n//g;
294                 s/#.*$//;
295                 local @w = split(/:/, $_, -1);
296                 if (@w == 6) {
297                         local $attr = { 'name' => $w[0],
298                                         'res1' => $w[1],
299                                         'res2' => $w[2],
300                                         'short' => $w[3],
301                                         'desc' => $w[4],
302                                         'line' => $lnum,
303                                         'type' => 'auth',
304                                         'index' => scalar(@list_auth_attrs_cache) };
305                         local $a;
306                         foreach $a (split(/;/, $w[5])) {
307                                 local ($an, $av) = split(/=/, $a, 2);
308                                 $attr->{'attr'}->{$an} = $av;
309                                 }
310                         push(@list_auth_attrs_cache, $attr);
311                         }
312                 $lnum++;
313                 }
314         close(ATTR);
315         $lnum++;
316         }
317 return \@list_auth_attrs_cache;
318 }
319
320 # create_auth_attr(&attr)
321 # Add a new authorization
322 sub create_auth_attr
323 {
324 local ($attr) = @_;
325 &list_auth_attrs();     # init cache
326 local $lref = &read_file_lines($config{'auth_attr'});
327 $attr->{'line'} = scalar(@$lref);
328 push(@$lref, &auth_attr_line($attr));
329 $attr->{'index'} = scalar(@list_auth_attrs_cache);
330 push(@list_auth_attrs_cache, $attr);
331 &flush_file_lines();
332 }
333
334 # modify_auth_attr(&attr)
335 # Updates an existing authorization in the config file
336 sub modify_auth_attr
337 {
338 local ($attr) = @_;
339 local $lref = &read_file_lines($config{'auth_attr'});
340 $lref->[$attr->{'line'}] = &auth_attr_line($attr);
341 &flush_file_lines();
342 }
343
344 # delete_auth_attr(&attr)
345 # Removes one authorization
346 sub delete_auth_attr
347 {
348 local ($attr) = @_;
349 local $lref = &read_file_lines($config{'auth_attr'});
350 splice(@$lref, $attr->{'line'}, 1);
351 splice(@list_auth_attrs_cache, $attr->{'index'}, 1);
352 local $c;
353 foreach $c (@list_auth_attrs_cache) {
354         $c->{'line'}-- if ($c->{'line'} > $attr->{'line'});
355         $c->{'index'}-- if ($c->{'index'} > $attr->{'index'});
356         }
357 &flush_file_lines();
358 }
359
360 # auth_attr_line(&attr)
361 # Returns the text for a auth attribute line
362 sub auth_attr_line
363 {
364 local ($attr) = @_;
365 local $rv = $attr->{'name'}.":".$attr->{'res1'}.":".$attr->{'res2'}.":".
366             $attr->{'short'}.":".$attr->{'desc'}.":";
367 $rv .= join(";", map { $_."=".$attr->{'attr'}->{$_} }
368                      keys %{$attr->{'attr'}});
369 return $rv;
370 }
371
372 # auths_input(name, value)
373 # Returns HTML for a text area for entering and choosing authorizations
374 sub auths_input
375 {
376 local ($name, $value) = @_;
377 return "<table cellpadding=0 cellspacing=0><tr><td>".
378        &ui_textarea($name, join("\n", split(/,/, $value)), 3, 50).
379        "</td><td valign=top>&nbsp;".
380        &auth_chooser($name).
381        "</td></tr></table>";
382 }
383
384 # auth_chooser(field)
385 # Returns HTML for a button that pops up an authorization chooser window
386 sub auth_chooser
387 {
388 return "<input type=button onClick='ifield = form.$_[0]; chooser = window.open(\"auth_chooser.cgi\", \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=500,height=400\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
389 }
390
391 # auths_parse(name)
392 # Returns a comma-separated list of auths
393 sub auths_parse
394 {
395 local @auths = split(/[\r\n]+/, $in{$_[0]});
396 local $a;
397 foreach $a (@auths) {
398         $a =~ /^\S+$/ || &error(&text('user_eauth', $a));
399         }
400 return join(",", @auths);
401 }
402
403 ####################### functions for exec attributes #######################
404
405 # list_exec_attrs()
406 # Returns a user of all execorizations
407 sub list_exec_attrs
408 {
409 if (!scalar(@list_exec_attrs_cache)) {
410         @list_exec_attrs_cache = ( );
411         local $lnum = 0;
412         open(ATTR, $config{'exec_attr'});
413         while(<ATTR>) {
414                 s/\r|\n//g;
415                 s/#.*$//;
416                 local @w = split(/:/, $_, -1);
417                 if (@w == 7) {
418                         local $attr = { 'name' => $w[0],
419                                         'policy' => $w[1],
420                                         'cmd' => $w[2],
421                                         'res1' => $w[3],
422                                         'res2' => $w[4],
423                                         'id' => $w[5],
424                                         'type' => 'exec',
425                                         'index' => scalar(@list_exec_attrs_cache) };
426                         local $a;
427                         foreach $a (split(/;/, $w[6])) {
428                                 local ($an, $av) = split(/=/, $a, 2);
429                                 $attr->{'attr'}->{$an} = $av;
430                                 }
431                         push(@list_exec_attrs_cache, $attr);
432                         }
433                 $lnum++;
434                 }
435         close(ATTR);
436         $lnum++;
437         }
438 return \@list_exec_attrs_cache;
439 }
440
441 # create_exec_attr(&attr)
442 # Add a new execorization
443 sub create_exec_attr
444 {
445 local ($attr) = @_;
446 &list_exec_attrs();     # init cache
447 local $lref = &read_file_lines($config{'exec_attr'});
448 $attr->{'line'} = scalar(@$lref);
449 push(@$lref, &exec_attr_line($attr));
450 $attr->{'index'} = scalar(@list_exec_attrs_cache);
451 push(@list_exec_attrs_cache, $attr);
452 &flush_file_lines();
453 }
454
455 # modify_exec_attr(&attr)
456 # Updates an existing execorization in the config file
457 sub modify_exec_attr
458 {
459 local ($attr) = @_;
460 local $lref = &read_file_lines($config{'exec_attr'});
461 $lref->[$attr->{'line'}] = &exec_attr_line($attr);
462 &flush_file_lines();
463 }
464
465 # delete_exec_attr(&attr)
466 # Removes one execorization
467 sub delete_exec_attr
468 {
469 local ($attr) = @_;
470 local $lref = &read_file_lines($config{'exec_attr'});
471 splice(@$lref, $attr->{'line'}, 1);
472 splice(@list_exec_attrs_cache, $attr->{'index'}, 1);
473 local $c;
474 foreach $c (@list_exec_attrs_cache) {
475         $c->{'line'}-- if ($c->{'line'} > $attr->{'line'});
476         $c->{'index'}-- if ($c->{'index'} > $attr->{'index'});
477         }
478 &flush_file_lines();
479 }
480
481 # exec_attr_line(&attr)
482 # Returns the text for a exec attribute line
483 sub exec_attr_line
484 {
485 local ($attr) = @_;
486 local $rv = $attr->{'name'}.":".$attr->{'policy'}.":".$attr->{'cmd'}.":".
487             $attr->{'res1'}.":".$attr->{'res2'}.":".$attr->{'id'}.":";
488 $rv .= join(";", map { $_."=".$attr->{'attr'}->{$_} }
489                      keys %{$attr->{'attr'}});
490 return $rv;
491 }
492
493 ####################### policy.conf functions #######################
494
495 # get_policy_config()
496 # Returns a list of policy config file directives
497 sub get_policy_config
498 {
499 if (!scalar(@policy_conf_cache)) {
500         @policy_conf_cache = ( );
501         local $lnum = 0;
502         open(ATTR, $config{'policy_conf'});
503         while(<ATTR>) {
504                 s/\r|\n//g;
505                 s/\s+$//;
506                 if (/^\s*(#*)\s*([^= ]+)\s*=\s*(\S.*)$/) {
507                         local $pol = { 'name' => $2,
508                                        'value' => $3,
509                                        'enabled' => !$1,
510                                        'line' => $lnum,
511                                        'index' => scalar(@policy_conf_cache) };
512                         push(@policy_conf_cache, $pol);
513                         }
514                 $lnum++;
515                 }
516         close(ATTR);
517         $lnum++;
518         }
519 return \@policy_conf_cache;
520 }
521
522 # find_policy(name, &conf, [enabled])
523 sub find_policy
524 {
525 local ($name, $conf, $ena) = @_;
526 local ($rv) = grep { lc($_->{'name'}) eq lc($name) &&
527                      (!defined($ena) ||
528                       defined($ena) && $ena == $_->{'enabled'}) } @$conf;
529 return $rv;
530 }
531
532 # find_policy_value(name, &conf);
533 sub find_policy_value
534 {
535 local ($name, $conf) = @_;
536 local $rv = &find_policy($name, $conf, 1);
537 return $rv ? $rv->{'value'} : undef;
538 }
539
540 # save_policy(&conf, name, [value])
541 # Update or delete an entry in policy.conf
542 sub save_policy
543 {
544 local ($conf, $name, $value) = @_;
545 local $old = &find_policy($name, $conf);
546 local $lref = &read_file_lines($config{'policy_conf'});
547 if (!$old && $value) {
548         # Need to add
549         push(@$lref, "$name=$value");
550         push(@$conf, { 'name' => $name,
551                        'value' => $value,
552                        'index' => scalar(@$conf),
553                        'line' => scalar(@$lref) - 1 });
554         }
555 elsif ($old && $value) {
556         # Need to update (and perhaps comment in)
557         $old->{'value'} = $value;
558         $old->{'enabled'} = 1;
559         $lref->[$old->{'line'}] = "$name=$value";
560         }
561 elsif ($old && $old->{'enabled'} && !$value) {
562         # Need to comment out
563         $old->{'enabled'} = 0;
564         $lref->[$old->{'line'}] = "#$name=$old->{'value'}";
565         }
566 }
567
568 ####################### functions for projects #######################
569
570 # list_projects()
571 # Returns a list of project objects
572 sub list_projects
573 {
574 if (!scalar(@list_projects_cache)) {
575         @list_projects_cache = ( );
576         local $lnum = 0;
577         open(ATTR, $config{'project'});
578         while(<ATTR>) {
579                 s/\r|\n//g;
580                 s/#.*$//;
581                 local @w = split(/:/, $_, -1);
582                 if (@w >= 2) {
583                         local $attr = { 'name' => $w[0],
584                                         'id' => $w[1],
585                                         'desc' => $w[2],
586                                         'users' => $w[3],
587                                         'groups' => $w[4],
588                                         'line' => $lnum,
589                                         'type' => 'project',
590                                         'index' => scalar(@list_projects_cache) };
591                         local $a;
592                         foreach $a (split(/;/, $w[5])) {
593                                 local ($an, $av) = split(/=/, $a, 2);
594                                 $attr->{'attr'}->{$an} = $av;
595                                 }
596                         push(@list_projects_cache, $attr);
597                         }
598                 $lnum++;
599                 }
600         close(ATTR);
601         }
602 return \@list_projects_cache;
603 }
604
605 # create_project(&attr)
606 # Add a new project
607 sub create_project
608 {
609 local ($attr) = @_;
610 &list_projects();       # init cache
611 local $lref = &read_file_lines($config{'project'});
612 $attr->{'line'} = scalar(@$lref);
613 push(@$lref, &project_line($attr));
614 $attr->{'index'} = scalar(@list_projects_cache);
615 push(@list_projects_cache, $attr);
616 &flush_file_lines();
617 }
618
619 # modify_project(&attr)
620 # Updates an existing project in the config file
621 sub modify_project
622 {
623 local ($attr) = @_;
624 local $lref = &read_file_lines($config{'project'});
625 $lref->[$attr->{'line'}] = &project_line($attr);
626 &flush_file_lines();
627 }
628
629 # delete_project(&attr)
630 # Removes one project entry
631 sub delete_project
632 {
633 local ($attr) = @_;
634 local $lref = &read_file_lines($config{'project'});
635 splice(@$lref, $attr->{'line'}, 1);
636 splice(@list_projects_cache, $attr->{'index'}, 1);
637 local $c;
638 foreach $c (@list_projects_cache) {
639         $c->{'line'}-- if ($c->{'line'} > $attr->{'line'});
640         $c->{'index'}-- if ($c->{'index'} > $attr->{'index'});
641         }
642 &flush_file_lines();
643 }
644
645 # project_line(&attr)
646 # Returns the text for a project file line
647 sub project_line
648 {
649 local ($attr) = @_;
650 local $rv = $attr->{'name'}.":".$attr->{'id'}.":".$attr->{'desc'}.":".
651             $attr->{'users'}.":".$attr->{'groups'}.":";
652 $rv .= join(";", map { defined($attr->{'attr'}->{$_}) ?
653                         $_."=".$attr->{'attr'}->{$_} : $_ }
654                      keys %{$attr->{'attr'}});
655 return $rv;
656 }
657
658 # project_input(name, value)
659 # Returns HTML for selecting one project
660 sub project_input
661 {
662 local ($name, $value) = @_;
663 local $projects = &list_projects();
664 return &ui_select($name, $value,
665                   [ map { [ $_->{'name'},
666                             $_->{'name'}.($_->{'desc'} ? " ($_->{'desc'})"
667                                                        : "") ] } @$projects ],
668                   0, 0, $value ? 1 : 0);
669 }
670
671 # project_members_input(name, comma-separated-members)
672 # Returns HTML for selecting users or groups in a project. These may be all,
673 # none, all except or only some
674 sub project_members_input
675 {
676 local ($name, $users) = @_;
677 local @users = split(/,/, $users);
678 local %users = map { $_, 1 } @users;
679 local (@canusers, @cannotuser, $mode);
680 if ($users{'*'} && @users == 1) {
681         $mode = 0;
682         }
683 elsif (@users == 0 || $users{'!*'} && @users == 1) {
684         $mode = 1;
685         }
686 elsif ($users{'*'}) {
687         # All except some
688         $mode = 3;
689         @cannotusers = map { /^\!(.*)/; $1 } grep { /^\!/ } @users[1..$#users];
690         }
691 elsif ($users{'!*'}) {
692         # Only some
693         $mode = 2;
694         @canusers = grep { !/^\!/ } @users[1..$#users];
695         }
696 else {
697         # Only listed
698         $mode = 2;
699         @canusers = @users;
700         }
701 local $cb = $name =~ /user/ ? \&user_chooser_button : \&group_chooser_button;
702 return &ui_radio($name."_mode", $mode,
703         [ [ 0, $text{'projects_all'.$name} ],
704           [ 1, $text{'projects_none'.$name}."<br>" ],
705           [ 2, &text('projects_only'.$name,
706              &ui_textbox($name."_can", join(" ", @canusers), 40)." ".
707              &$cb($name."_can", 1))."<br>" ],
708           [ 3, &text('projects_except'.$name,
709              &ui_textbox($name."_cannot", join(" ", @cannotusers), 40)." ".
710              &$cb($name."_cannot", 1)) ]
711         ]);
712 }
713
714 # parse_project_members(name)
715 sub parse_project_members
716 {
717 local ($name) = @_;
718 if ($in{$name."_mode"} == 0) {
719         return "*";
720         }
721 elsif ($in{$name."_mode"} == 1) {
722         return "";
723         }
724 elsif ($in{$name."_mode"} == 2) {
725         $in{$name."_can"} || &error($text{'project_e'.$name.'can'});
726         return join(",", split(/\s+/, $in{$name."_can"}));
727         }
728 elsif ($in{$name."_mode"} == 3) {
729         $in{$name."_cannot"} || &error($text{'project_e'.$name.'cannot'});
730         return join(",", "*", map { "!$_" } split(/\s+/, $in{$name."_cannot"}));
731         }
732 }
733
734 ####################### miscellaneous functions #######################
735
736 # nice_comma_list(comma-separated-value)
737 # Nicely formats and shortens a comma-separated string
738 sub nice_comma_list
739 {
740 local @l = split(/,/, $_[0]);
741 if (@l > 2) {
742         return join(" , ", @l[0..1], "...");
743         }
744 else {
745         return join(" , ", @l);
746         }
747 }
748
749 # rbac_help_file(&object)
750 sub rbac_help_file
751 {
752 local $hf = $config{$_[0]->{'type'}.'_help_dir'}."/$_[0]->{'attr'}->{'help'}";
753 return -r $hf && !-d $hf ? $hf : undef;
754 }
755
756 # rbac_help_link(&object, desc)
757 sub rbac_help_link
758 {
759 local $hf = &rbac_help_file($_[0]);
760 local $rv = "<table cellpadding=0 cellspacing=0 width=100%><tr><td>$_[1]</td>";
761 if ($hf) {
762         $hf = &urlize($hf);
763         $rv .= "<td align=right><a onClick='window.open(\"rbac_help.cgi?help=$hf\", \"help\", \"toolbar=no,menubar=no,scrollbars=yes,width=400,height=300,resizable=yes\"); return false' href=\"rbac_help.cgi?help=$hf\"><img src=images/help.gif border=0></a></td>";
764         }
765 $rv .= "</tr></table>";
766 return $rv;
767 }
768
769 # rbac_config_files()
770 # Returns a list of all config files managed by this module
771 sub rbac_config_files
772 {
773 return map { $config{$_} } ('user_attr', 'prof_attr', 'auth_attr',
774                             'exec_attr', 'policy_conf', 'project');
775 }
776
777 # missing_rbac_config_file()
778 # Returns the path to any missing config file
779 sub missing_rbac_config_file
780 {
781 foreach $c (&rbac_config_files()) {
782         return $c if (!-r $c);
783         }
784 return undef;
785 }
786
787 # lock_rbac_files()
788 # Lock all config files used by RBAC
789 sub lock_rbac_files
790 {
791 local $c;
792 foreach $c (&rbac_config_files()) {
793         &lock_file($c);
794         }
795 }
796
797 # unlock_rbac_files()
798 # Unlock all config files used by RBAC
799 sub unlock_rbac_files
800 {
801 local $c;
802 foreach $c (&rbac_config_files()) {
803         &unlock_file($c);
804         }
805 }
806
807 # list_crypt_algorithms()
808 # Returns 1 list of all encryption algorithms, including the internal __unix__
809 sub list_crypt_algorithms
810 {
811 if (!scalar(@list_crypt_algorithms_cache)) {
812         push(@list_crypt_algorithms_cache, { 'name' => '__unix__' } );
813         local $lnum = 0;
814         open(CRYPT, $config{'crypt_conf'});
815         while(<CRYPT>) {
816                 s/\r|\n//g;
817                 s/#.*$//;
818                 if (/^\s*(\S+)\s+(\S+)/) {
819                         push(@list_crypt_algorithms_cache,
820                            { 'name' => $1,
821                              'lib' => $2,
822                              'line' => $lnum,
823                              'index' => scalar(@list_crypt_algorithms_cache) });
824                         }
825                 $lnum++;
826                 }
827         close(CRYPT);
828         }
829 return \@list_crypt_algorithms_cache;
830 }
831
832 # crypt_algorithms_input(name, value, multiple)
833 # Returns HTML for selecting one or many crypt algorithms
834 sub crypt_algorithms_input
835 {
836 local ($name, $value, $multiple) = @_;
837 local @values = split(/,/, $value);
838 local $crypts = &list_crypt_algorithms();
839 return &ui_select($name, \@values,
840                   [ map { [ $_->{'name'}, $text{'crypt_'.$_->{'name'}} ] }
841                         @$crypts ], $multiple ? scalar(@$crypts) : undef,
842                         $multiple, 1);
843 }
844
845 # can_edit_user(&user)
846 # Returns 1 if some user can be edited
847 sub can_edit_user
848 {
849 local ($user) = @_;
850 return 0 if ($user->{'attr'}->{'type'} eq 'role' && !$access{'roles'});
851 return 0 if ($user->{'attr'}->{'type'} ne 'role' && !$access{'users'});
852 return 1;
853 }
854
855 # can_assign_role(&role|rolename)
856 # Returns 1 if some role can be assigned
857 sub can_assign_role
858 {
859 local ($role) = @_;
860 local $rolename = ref($role) ? $role->{'user'} : $role;
861 if ($access{'roleassign'} eq '*') {
862         return 1;
863         }
864 else {
865         local @canroles;
866         if ($access{'roleassign'} eq 'x') {
867                 # Work out Webmin user's roles
868                 @canroles = &all_recursive_roles($remote_user);
869                 }
870         else {
871                 @canroles = split(/,/, $access{'roleassign'});
872                 }
873         return &indexof($rolename, @canroles) != -1;
874         }
875 }
876
877 # can_assign_profile(&profile|profilename)
878 # Returns 1 if some profile can be assigned
879 sub can_assign_profile
880 {
881 local ($prof) = @_;
882 local $profname = ref($prof) ? $prof->{'name'} : $prof;
883 if ($access{'profassign'} eq '*') {
884         return 1;
885         }
886 else {
887         local @canprofs;
888         if ($access{'profassign'} eq 'x') {
889                 # Work out Webmin user's profs
890                 @canprofs = &all_recursive_profs($remote_user);
891                 }
892         else {
893                 @canprofs = split(/,/, $access{'profassign'});
894                 }
895         return &indexof($profname, @canprofs) != -1;
896         }
897 }
898
899 # list_rctls()
900 # Returns a list of possible resource control names
901 sub list_rctls
902 {
903 local @rv;
904 open(RCTL, "rctladm -l |");
905 while(<RCTL>) {
906         if (/^(\S+)\s+(\S+)=(\S+)/) {
907                 push(@rv, $1);
908                 }
909         }
910 close(RCTL);
911 return @rv;
912 }
913
914 # list_rctl_signals()
915 # Returns a list of allowed signals for rctl actions
916 sub list_rctl_signals
917 {
918 return ( [ "SIGABRT", "Abort the process" ],
919          [ "SIGHUP", "Send a hangup signal" ],
920          [ "SIGTERM", "Terminate the process" ],
921          [ "SIGKILL", "Kill the process" ],
922          [ "SIGSTOP", "Stop the process" ],
923          [ "SIGXRES", "Resource control limit exceeded" ],
924          [ "SIGXFSZ", "File size limit exceeded" ],
925          [ "SIGXCPU", "CPU time limit exceeded" ] );
926 }
927
928 # list_resource_controls(type, id)
929 # Returns a list of resource controls for some project, zone or process
930 sub list_resource_controls
931 {
932 local ($type, $id) = @_;
933 &open_execute_command(PRCTL,
934                       "prctl -i ".quotemeta($type)." ".quotemeta($id), 1);
935 local (@rv, $res);
936 while(<PRCTL>) {
937         s/\r|\n//g;
938         next if (/^NAME/);      # skip header
939         if (/^(\S+)\s*$/) {
940                 # Start of a new resource
941                 $res = $1;
942                 }
943         elsif (/^\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ && $res) {
944                 # A limit within a resource
945                 push(@rv, { 'res' => $res,
946                             'priv' => $1,
947                             'limit' => $2,
948                             'flag' => $3 eq "-" ? undef : $3,
949                             'action' => $4 });
950                 }
951         }
952 close(PRCTL);
953 return @rv;
954 }
955
956 1;
957