#!/usr/bin/perl use strict; use diagnostics; use lib qw(/usr/lib/libDrakX); use standalone; use MDK::Common; use common; use mygtk2 qw(gtknew gtkset); use ugtk2 qw(:create :dialogs :helpers :wrappers); $ugtk2::wm_icon = "/usr/share/mcc/themes/default/drakperm-mdk.png"; require_root_capability(); #- vars declaration my ($level) = chomp_(`cat /etc/sysconfig/msec | grep SECURE_LEVEL= |cut -d= -f2`); my $default_perm_level = "level " . $level; my %perm_files = ($default_perm_level => '/usr/share/msec/perm.' . $level, 'editable' => '/etc/security/msec/perm.local', ); my %perm_l10n = ($default_perm_level => N("System settings"), 'editable' => N("Custom settings"), 'all' => N("Custom & system settings"), ); my %rev_perm_l10n = reverse %perm_l10n; my ($editable, $modified) = (0, 0); my @rules; #- Widget declaration my $w = ugtk2->new(N("Security Permissions")); $w->{rwindow}->set_size_request(620, 400) unless $::isEmbedded; my $W = $w->{window}; $W->signal_connect(delete_event => sub { ugtk2->exit }); my $model = Gtk2::ListStore->new("Gtk2::Gdk::Pixbuf", ("Glib::String") x 5); my $permList = Gtk2::TreeView->new_with_model($model); my $pixbuf = gtknew('Pixbuf', file => 'non-editable'); my @column_sizes = (150, 100, 100, 15, -1); # TreeView layout is (Editable, Path, User, Group, Permissions, [hidden]index_id) $permList->append_column(Gtk2::TreeViewColumn->new_with_attributes(N("Editable"), Gtk2::CellRendererPixbuf->new, 'pixbuf' => 0)); each_index { my $col = Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::CellRendererText->new, 'text' => $::i + 1); $col->set_min_width($column_sizes[$::i+1]); $permList->append_column($col); } (N("Path"), N("User"), N("Group"), N("Permissions")); my $index = 0; load_perms(); #- widgets settings my $combo_perm_value = $perm_l10n{all}; my $combo_perm = gtknew('ComboBox', list => [ sort(values %perm_l10n) ], text_ref => \$combo_perm_value); sub add_callback() { row_setting_dialog(N("Add a new rule")); $modified++; } sub edit_callback() { my (undef, $iter) = $permList->get_selection->get_selected; return unless $iter; row_setting_dialog(N("Edit current rule"), $iter); } my @buttons; sub del_callback() { my ($tree, $iter) = $permList->get_selection->get_selected; my $removed_idx = $tree->get($iter, 5); @rules = grep { $_->{index} ne $removed_idx } @rules; $tree->remove($iter); sensitive_buttons(0); $modified++; } sub move_callback { my ($direction) = @_; my ($model, $iter) = $permList->get_selection->get_selected; return if !$iter; my $path = $model->get_path($iter) or return; $direction eq 'up' ? $path->prev : $path->next; my $iter2 = $model->get_iter($path); return if !$iter2 || $model->get($iter2, 0); $model->swap($iter, $iter2); $modified = 1; hide_up_button_iffirst_item($path); hide_down_button_iflast_item($path); $permList->get_selection->select_iter($iter); $permList->queue_draw; } $permList->signal_connect(button_press_event => sub { return unless $editable; my (undef, $event) = @_; my (undef, $iter) = $permList->get_selection->get_selected; return unless $iter; row_setting_dialog(N("Edit current rule"), $iter) if $event->type eq '2button-press'; }); my $tips = Gtk2::Tooltips->new; $W->add(gtknew('VBox', spacing => 5, children => [ if_(!$::isEmbedded, 0, Gtk2::Banner->new('/usr/share/mcc/themes/default/drakperm-mdk.png', N("Permissions"))), 0, gtknew('Label_Left', text => N("Here you can see files to use in order to fix permissions, owners, and groups via msec.\nYou can also edit your own rules which will overwrite the default rules."), alignment => [ 0.5, 0 ]), 1, gtknew('VBox', border_width => 0, children => [ 0, gtknew('Label_Left', text => N("The current security level is %s. Select permissions to see/edit", $level), alignment => [ 0, 0 ]), 0, gtknew('HButtonBox', layout => 'start', children => [ 0, $combo_perm, ]), 0, gtknew('Label'), 1, gtknew('ScrolledWindow', child => $permList), 0, my $up_down_box = gtknew('HBox', spacing => 5, children_loose => [ @buttons = map { gtkset_tip($tips, gtknew('Button', text => $_->[0], clicked => $_->[2]), $_->[1]); } ([ N("Up"), N("Move selected rule up one level"), sub { move_callback('up') } ], [ N("Down"), N("Move selected rule down one level"), sub { move_callback('down') } ], [ N("Add a rule"), N("Add a new rule at the end"), \&add_callback ], [ N("Delete"), N("Delete selected rule"), \&del_callback ], [ N("Edit"), N("Edit current rule"), \&edit_callback ]) ]), 0, gtknew('VBox'), ]), 0, create_okcancel({ cancel_clicked => sub { ugtk2->exit }, ok_clicked => \&save_perm, }, undef, undef, '', [ N("Help"), sub { run_program::raw({ detach => 1 }, 'drakhelp', '--id', 'drakperm') } ], ) ]) ); $W->show_all; $w->{rwindow}->set_position('center') unless $::isEmbedded; display_perm('all'); my $_combo_sig = $combo_perm->entry->signal_connect(changed => sub { my $class = $rev_perm_l10n{$combo_perm_value}; $permList->set_reorderable($class eq 'editable'); display_perm($class , @_); }); $permList->get_selection->signal_connect('changed' => sub { my ($select) = @_; my (undef, $iter) = $select->get_selected; return if !$iter; my $locked = $model->get($iter, 0); sensitive_buttons($iter ? $editable && !$locked : 0); return if $locked; my $curr_path = $model->get_path($iter); hide_up_button_iffirst_item($curr_path); hide_down_button_iflast_item($curr_path); }); $w->main; ugtk2->exit; sub hide_up_button_iffirst_item { my ($curr_path) = @_; my $first_path = $model->get_path($model->get_iter_first); $buttons[0]->set_sensitive($first_path && $first_path->compare($curr_path)); } sub hide_down_button_iflast_item { my ($curr_path) = @_; $curr_path->next; my $next_item = $model->get_iter($curr_path); $buttons[1]->set_sensitive($next_item && !$model->get($next_item, 0)); } sub display_perm { my ($perm_level) = @_; return unless $perm_level; my $show_sys_rules = $perm_level eq $default_perm_level; my $show_user_rules = $perm_level eq 'editable'; my $show_all_rules = $perm_level eq 'all'; # cleaner way: only remove filtered out rules, add those not any more filtered rather than refilling the whole tree $model->clear; foreach my $rule (@rules) { next if !$show_all_rules && ($show_user_rules && $rule->{editable} || $show_sys_rules && !$rule->{editable}); $model->append_set(map_index { if_(defined $rule->{$_}, $::i => $rule->{$_}) } qw(editable path user group perms index)); } # alter button box behavior $editable = $perm_level =~ /^level \d/ ? 0 : 1; $up_down_box->set_sensitive($editable); sensitive_buttons(0) if $editable; } sub save_perm() { my $val; if ($modified) { my $F; open $F, '>' . $perm_files{editable} or die(qq(Impossible to process "$perm_files{editable}")); $model->foreach(sub { my ($model, $_path, $iter) = @_; return 0 if $model->get($iter, 0); my $line = $model->get($iter, 1) . "\t" . $model->get($iter, 2) . ($model->get($iter, 3) ? "." . $model->get($iter, 3) : "") . "\t" . $model->get($iter, 4) . "\n"; print $F $line; return 0; }, $val); close $F; } $modified = 0; ugtk2->exit; } sub load_perms() { foreach my $file (@perm_files{($default_perm_level, 'editable')}) { my @editable = if_($file ne $perm_files{editable}, editable => $pixbuf); local $_; foreach (cat_($file)) { next if /^#/; # Editable, Path, User, Group, Permissions if (m/^(\S+)\s+([^.\s]+)\.(\S+)?\s+(\d+)/) { push @rules, { @editable, path => $1, user => $2, group => $3, perms => $4, index => $index }; } elsif (m/^(\S+)\s+current?\s+(\d+)/) { push @rules, { @editable, path => $1, user => 'current', group => '', perms => $2, index => $index }; } else { warn qq(unparsable "$_"line); } $index++; } } } sub row_setting_dialog { my ($title, $o_iter) = @_; my $dlg = gtknew('Dialog', transient_for => $w->{real_window}, title => $title); # $dlg->set_resizable(0); my $browse = gtknew('Button', text => N("browse")); my $file = gtknew('Entry', $o_iter ? (text => $model->get($o_iter, 1)) : ()); my ($other, $group, $user, $s) = $o_iter ? reverse(split(//, $model->get($o_iter, 4))) : (); my @bits = qw(sticky gid suid); my @rights = qw(read write execute); my @owners = (N_("user"), N_("group"), N_("other")); my %rights = (user => $user, group => $group, other => $other); my %rights_labels = (user => N("User"), group => N("Group"), other => N("Other")); my %checks = ('read' => { label => N("Read"), tip => { map { $_ => #-PO: here %s will be either "user", "group" or "other" N("Enable \"%s\" to read the file", translate($_)); } keys %rights }, }, 'write' => { label => N("Write"), tip => { map { $_ => #-PO: here %s will be either "user", "group" or "other" N("Enable \"%s\" to write the file", translate($_)); } keys %rights }, }, 'execute' => { label => N("Execute"), tip => { map { $_ => #-PO: here %s will be either "user", "group" or "other" N("Enable \"%s\" to execute the file", translate($_)); } keys %rights }, }, sticky => { label => N("Sticky-bit"), tip => N("Used for directory:\n only owner of directory or file in this directory can delete it") }, suid => { label => N("Set-UID"), tip => N("Use owner id for execution") }, gid => { label => N("Set-GID"), tip => N("Use group id for execution") }, ); #- dlg widgets settings my %s_right = get_right($s); my $alrd_exsts = defined $o_iter; my $users = gtknew('ComboBox', list => [ my @users = get_user_or_group('users') ]); $users->entry->set_text($model->get($o_iter, 2)) if $o_iter; $users->set_wrap_width(3); my $groups = gtknew('ComboBox', list => [ my @groups = get_user_or_group('groups') ]); $groups->entry->set_text($model->get($o_iter, 3)) if $o_iter; $groups->set_wrap_width(3); my $id_box = gtknew('Table', homogeneous => 0, xpadding => 0.1, ypadding => 0, border_width => 0, children => [ [ gtknew('Label_Left', text => N("User:")), $users ], [ gtknew('Label_Left', text => N("Group:")), $groups ], ] ); my $usr_check = gtksignal_connect(gtkset_tip($tips, gtknew('CheckButton', text => N("Current user")), N("When checked, owner and group will not be changed")), clicked => sub { $id_box->set_sensitive(!$_[0]->get_active) }); if ($o_iter && $model->get($o_iter, 2) eq 'current') { $usr_check->set_active(1); $id_box->set_sensitive(0); } else { $usr_check->set_active(0) } $browse->signal_connect(clicked => sub { my $file_dlg = gtknew('FileChooserDialog', title => N("Path selection"), action => 'select_folder', modal => 1, transient_for => $dlg); $file_dlg->set_filename($file->get_text); $file_dlg->show; my $answer = $file_dlg->run; if ($answer eq 'ok') { $file->set_text($file_dlg->get_filename); } $file_dlg->hide; $file_dlg->destroy; }); my %perms; gtkpack_($dlg->vbox, 0, gtknew('Title2', label => N("Path")), 0, gtknew('HBox', border_width => 18, children => [ 1, $file, 0, $browse ] ), 0, gtknew('Title2', label => N("Property")), 0, gtknew('VBox', border_width => 18, children => [ 0, $usr_check, ] ), 0, $id_box, 0, gtknew('Title2', label => N("Permissions")), 1, gtknew('HBox', border_width => 0, children_loose => [ gtknew('VBox', border_width => 0, children_loose => [ gtknew('Label', text => ""), map { gtknew('Label_Left', text => $checks{$_}{label}, alignment => [ 0, 0 ]) } @rights, ]), (map { my $owner = $_; $perms{$owner} = { get_right($rights{$owner}) }; my $vbox = gtknew('VBox', children_loose => [ gtknew('Label', text => $rights_labels{$owner}), map { my $c = $_; my $active = $perms{$owner}{$c}; $perms{$owner}{$c} = gtknew('CheckButton'); $tips->set_tip($perms{$owner}{$c}, $checks{$c}{tip}{$owner}, ); gtkset_active($perms{$owner}{$c}, $active); } @rights, ]); $vbox; } @owners), gtknew('VBox', children_loose => [ gtknew('Label', text => ' '), map { $perms{$_} = gtkset(gtknew('CheckButton', text => $checks{$_}{label}), tip => $checks{$_}{tip}) } @bits, ]), ]), ); $perms{sticky}->set_active($s_right{execute}); $perms{gid}->set_active($s_right{write}); $perms{suid}->set_active($s_right{read}); $dlg->set_has_separator(0); gtkadd($dlg->action_area, create_okcancel(my $w = { cancel_clicked => sub { $dlg->destroy }, ok_clicked => sub { my ($path, $user, $group, $perms, $_idx); $path = $file->get_text; if ($path !~ m!^/!) { err_dialog(N("Warning"), N("The first character of the path must be a slash (\"/\"):\n\"%s\"", $path)); return 1; } if ($usr_check->get_active) { $user = 'current'; $group = ''; } else { $user = $users->entry->get_text; $group = $groups->entry->get_text; if (!member($user, @users) || !member($group, @groups)) { err_dialog(N("Warning"), join("\n", N("Both the username and the group must valid!"), N("User: %s", $user), N("Group: %s", $group), ) ); return 1; } } $perms = sprintf("%03o", eval(join('', "0b", (map { $perms{$_}->get_active || 0 } reverse @bits), (map { my $owner = $_;map_index { $perms{$owner}{$_}->get_active || 0; } @rights } @owners)))); # create new item if needed (that is when adding a new one) at end of list if (!$o_iter) { $o_iter = $model->append; push @rules, { path => $path, user => $user, group => $group, perms => $perms, index => $index }; $model->set($o_iter, 5 => $index++); } $model->set($o_iter, 1 => $path, 2 => $user, 3 => $group, 4 => $perms); $dlg->destroy; $modified++; } }, ), ); $w->{ok}->set_sensitive(!$model->get($o_iter, 0)) if $alrd_exsts; $dlg->show_all; } sub get_user_or_group { my ($what) = @_; my @users; local $_; my $is_users = $what eq 'users'; foreach (cat_($is_users ? '/etc/passwd' : '/etc/group')) { if ($is_users) { push @users, $1 if m/^([^#:]+):[^:]+:[^:]+:/; # or next; } else { push @users, $1 if m/^([^#:]+):[^:]*:[^:]*:/; # or next; } } return sort(@users); } sub get_right { my ($right) = @_; my %rght = ('read' => 0, 'write' => 0, 'execute' => 0); $right - 4 >= 0 and $rght{read}=1 and $right = $right-4; $right - 2 >= 0 and $rght{write}=1 and $right = $right-2; $right - 1 >= 0 and $rght{execute}=1 and $right = $right-1; return %rght; } sub sensitive_buttons { foreach my $i (0, 1, 3, 4) { $buttons[$i]->set_sensitive($_[0]); } }