#!/usr/bin/perl ################################################################################ # # # # # Copyright (C) 2003-2009 Mandriva # # # # Daouda Lo # # Thierry Vignaud # # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License Version 2 as # # published by the Free Software Foundation. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program; if not, write to the Free Software # # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ################################################################################ use strict; use lib qw(/usr/lib/libDrakX); # i18n: IMPORTANT: to get correct namespace (userdrake instead of libDrakX) BEGIN { unshift @::textdomains, 'userdrake', 'libuser', 'drakconf' } use standalone; use transfugdrake; use common; use run_program; use any; use mygtk3 qw(gtknew); #- do not import gtkadd which conflicts with ugtk3 version use ugtk3 qw(:all); use interactive; use POSIX; use USER; #Only for Debugging #use Devel::Peek; use utf8; use log; $ugtk3::wm_icon = "userdrake"; my $conffile = '/etc/sysconfig/userdrake'; my $pixdir = '/usr/share/userdrake/pixmaps/'; my @pix = ($pixdir . 'selected.png', $pixdir . 'unselected.png'); my $in = interactive->vnew('su'); my $us = {}; $us->{VERSION} = '1.13.3'; my $minimum_id = 1000; my $window_splash = Gtk3::Window->new('popup'); $window_splash->signal_connect(delete_event => \&QuitGlobal); $window_splash->set_title(N("Userdrake") . $us->{VERSION}); $window_splash->set_position('center_always'); $window_splash->add(gtkadd(gtkset_shadow_type(Gtk3::Frame->new, 'etched_out'), gtkpack(Gtk3::VBox->new(0, 0), gtkcreate_img("$pixdir/userdrake.png"), Gtk3::Label->new(N("Loading Users and Groups... Please wait")) ) ) ); $window_splash->show_all; gtkflush(); #my $wait = $in->wait_message(N("Please wait"), N("Loading User and Groups")); #gtkflush(); my $error = 0; my $GetValue = -65533; my $stringsearch = ''; my %prefs = getVarsFromSh($conffile); my $sysfilter = text2bool($prefs{FILTER}); sub HelpSystem() { run_program::raw({ detach => 1 }, 'drakhelp', '--id', 'userdrake') } $us->{wnd} = ugtk3->new(N("Mageia Users Management Tool") . " " . $us->{VERSION}); $::main_window = $us->{wnd}{real_window}; gtkset_size_request($us->{wnd}{rwindow}, 660, 460); $us->{wnd}{rwindow}->set_position('center') if !$::isEmbedded; $us->{wnd}{window}->signal_connect(delete_event => \&QuitGlobal); my $utree_model = Gtk3::ListStore->new("Glib::String", "Glib::Int", "Glib::String", "Glib::String", "Glib::String", "Glib::String", "Glib::String"); my $gtree_model = Gtk3::ListStore->new("Glib::String", "Glib::Int", "Glib::String"); my ($usertree, $grouptree); $usertree = CreateTree($utree_model); $grouptree = CreateTree($gtree_model); # slightly verbatimed from control-center my %xguest_labels = ( to_install => N("_Install guest account"), installed => N("_Uninstall guest account"), ); my %options = ( 'edit' => N("_Edit"), 'delete' => N("_Delete"), 'xguest' => $xguest_labels{installed}, 'filter' => N("_Filter system users") ); my %option_paths = ( 'edit' => 'Actions_Menu', 'delete' => 'Actions_Menu', 'xguest' => 'Actions_Menu', 'filter' => 'Options_Menu' ); my %buttorcheck; my $ui = gtknew('UIManager', actions => [ # [name, stock_id, value, label, accelerator, tooltip, callback] [ 'FileMenu', undef, N("_File") ], [ 'Refresh', undef, N("_Refresh"), undef, undef, sub { Refresh($sysfilter, $stringsearch) } ], [ 'Quit', undef, N("_Quit"), N("Q"), undef, \&QuitGlobal ], [ 'Actions_Menu', undef, N("_Actions") ], [ 'Add_User', undef, N("_Add User"), undef, undef, \&AddUser ], [ 'Add_Group', undef, N("Add _Group"), undef, undef, \&AddGroup ], [ 'edit', undef, $options{edit}, undef, undef, \&Edit ], [ 'delete', undef, $options{delete}, undef, undef, \&Delete ], [ 'xguest', undef, $options{xguest}, undef, undef, \&Xguest ], [ 'Options_Menu', undef, N("_Options") ], [ 'Help_Menu', undef, N("_Help") ], [ 'Help', undef, N("_Help"), undef, undef, sub { HelpSystem() } ], [ 'Report_Bug', undef, N("_Report Bug"), undef, undef, sub { run_program::raw({ detach => 1 }, 'drakbug', '--report', 'userdrake') } ], [ 'About', undef, N("_About..."), undef, undef, \&About ] ], toggle_actions => [ [ 'filter', undef, $options{filter}, undef, undef, sub { $sysfilter = $buttorcheck{filter}->get_active; Refresh($sysfilter, $stringsearch); } ], ], string => qq( )); my $window = $::isEmbedded ? $::Plug : $us->{wnd}{rwindow}; $window->add_accel_group($ui->get_accel_group); %buttorcheck = map { $_ => $ui->get_widget(join('/', '/MenuBar', $option_paths{$_}, $_)) } ('edit', 'delete', 'filter', 'xguest'); if (defined $buttorcheck{filter}) { $buttorcheck{filter}->set_active($sysfilter); } else { print STDERR "BUG with LANGUAGE $ENV{LANGUAGE}\n"; } my $toolb = Gtk3::Toolbar->new; my $filter; my $searchBox = gtkpack_(Gtk3::HBox->new(0,5), 1, Gtk3::Label->new(""), 0, Gtk3::Label->new(N("Search:")), 0, gtksignal_connect($filter = Gtk3::Entry->new, key_press_event => sub { $_[1]->keyval == Gtk3::Gdk::KEY_Return and Refresh($sysfilter, $filter->get_text); }), 0, my $fbut = Gtk3::Button->new(N("Apply filter")), ); gtkappend_page(my $nb = Gtk3::Notebook->new, gtkpack(create_scrolled_window($usertree)), gtkshow(Gtk3::Label->new(N("Users")))); #PO: list of users belonging to that group gtkappend_page($nb, gtkpack(create_scrolled_window($grouptree)), gtkshow(Gtk3::Label->new(N("Groups")))); $nb->set_show_border(0); my @extra_widgets; if ($::isEmbedded) { push @extra_widgets, 0, Gtk3::Banner->new("/usr/share/mcc/themes/default/user-mdk.png", #-PO: do not translate, this is already translated in mcc N("Users and groups")); } $us->{wnd}{window}->add(gtkpack_(Gtk3::VBox->new(0, 0), 0, $ui->get_widget('/MenuBar'), @extra_widgets, 0, $toolb, 0, $searchBox, 0, Gtk3::HSeparator->new, 1, $nb)); my @ucolsize = (60, 45, 40, 120, 80, 120, 50, -1); my @gcolsize = (100, 80, 160, -1); each_index { my $col = Gtk3::TreeViewColumn->new_with_attributes($_, Gtk3::CellRendererText->new, 'text' => $::i); $col->set_sort_column_id($::i); $col->set_min_width($ucolsize[$::i]); $usertree->append_column($col); } (N("User Name"), N("User ID"), N("Primary Group"), N("Full Name"), N("Login Shell"), N("Home Directory"), N("Status")); each_index { my $col = Gtk3::TreeViewColumn->new_with_attributes($_, Gtk3::CellRendererText->new, 'text' => $::i); $col->set_sort_column_id($::i); $col->set_min_width($gcolsize[$::i]); $grouptree->append_column($col); } (N("Group Name"), N("Group ID"), N("Group Members")); my @toolbwg; foreach ([ N("Add User"), N("Add a user to the system"), 'user_add', \&AddUser ], [ N("Add Group"), N("Add a group to the system"), 'group_add', \&AddGroup ], [ N("Edit"), N("Edit selected row"), 'user_conf', \&Edit ], [ N("Delete"), N("Delete selected row"), 'user_del', \&Delete ], [ N("Refresh"), N("Refresh the list"), 'refresh', sub { Refresh($sysfilter, $stringsearch) } ]) { my $t = gtknew('ToolButton', label => $_->[0], tooltip => $_->[1], file => $pixdir . $_->[2] . '.png', clicked => $_->[3]); $toolb->insert($t, -1); push(@toolbwg, $t); # $toolb->append_space; } my ($_tbuser, $_tbgroup, $tbedit, $tbdel, $_tbref) = @toolbwg; GrayDelEdit(); RefreshXguest(); my $ctx = USER::ADMIN->new; $fbut->signal_connect('clicked', sub { $stringsearch = $filter->get_text; Refresh($sysfilter, $stringsearch); }); Refresh($sysfilter, $stringsearch); $nb->signal_connect('switch-page' => sub { NotebookSwitch() }); $us->{wnd}{rwindow}->show_all; #undef $wait; $window_splash->destroy; undef $window_splash; $us->{wnd}->main; ugtk3->exit(0); =head1 NAME userdrake - Mageia Users Management Tool =head1 SYNOPSIS userdrake drakuser (alias to userdrake) =head1 DESCRIPTION This script manages user accounts for your Mageia installation. It requires the root password. =head1 FUNCTIONS =over =item C This function parses and returns data from a text file in the format: # Parameters and values are separated by whitespaces. PARAMETER1 VALUE1 PARAMETER2 VALUE2 ... or # Parameters and values are separated by the '=' symbol. PARAMETER1=VALUE1 PARAMETER2=VALUE2 ... It is somehow similar to L except that get_params() is also able to parse files where whitespaces are used as separator. Maybe one day both functions will be merged. Params: $file - The name of the text file to parse. @parameters - The list of parameters for which you want their value. Returns: A hashref in the form { PARAMETER1 => VALUE1, PARAMETER2 => VALUE2, ... }. =item C Make sure that the password is at least 6 characters long if the security level specified in /etc/security/msec/security.conf is higher than 'standard'. This function is based on ManaTools::Shared::Users Params: $passwd - The password to check. Returns: TRUE if the password is too weak for the current security level. FALSE otherwise. =back =cut sub is_xguest_installed() { -e '/etc/security/namespace.d/xguest.conf'; } sub get_params { my ($file, @parameters) = @_; if (open(my $fh, '<', $file)) { my @lines = <$fh>; close $fh; my $param_list = join('|', @parameters); my %params = map { /^($param_list)\b(?:=|\s+)(.+)$/; $1 => $2 } grep { /^(?:$param_list)\b/ } @lines; return \%params; } return {}; } sub weakPasswordForSecurityLevel { my $password = shift; my $level = get_params('/etc/security/msec/security.conf', qw(BASE_LEVEL))->{BASE_LEVEL}; if (!$level || $level eq 'none' || $level eq 'standard' || length($password) >= 6) { return 0; } return 1; } sub GrayDelEdit() { foreach ($tbedit, $tbdel, $buttorcheck{edit}, $buttorcheck{delete}) { defined $_ and $_->set_sensitive(0); } } sub RefreshXguest { my ($o_is_already_refreshed) = @_; my $label = $xguest_labels{is_xguest_installed() ? 'installed' : 'to_install'}; $label =~ s!^/!!; $buttorcheck{xguest}->get_child->set_label($label); Refresh($sysfilter, $stringsearch) if !$o_is_already_refreshed; } sub TreeUnselect { my $treev = shift; $treev->get_selection->unselect_all; GrayDelEdit(); RefreshXguest(); } sub NotebookSwitch() { #my $page = $nb->get_current_page; TreeUnselect($usertree); TreeUnselect($grouptree); } sub ComputeLockExpire { my ($l) = @_; my $ep = $l->ShadowExpire($GetValue); my $tm = ceil(time()/(24*60*60)); $ep = -1 if int($tm) <= $ep; my $status = $ctx->IsLocked($l) ? N("Locked") : ($ep != -1 ? N("Expired") : ''); $status; } sub RefreshUsersFull { my ($filterusers, $strfilt) = @_; my ($users, $group, $groupnm, $expr); defined $ctx and $users = $ctx->UsersEnumerateFull; $utree_model->clear; my @UserReal; LOOP: foreach my $l (@$users) { next LOOP if $filterusers && $l->Uid($GetValue) <= 499 || $l->Uid($GetValue) == 65534; next LOOP if $filterusers && $l->Uid($GetValue) > 499 && $l->Uid($GetValue) < 1000 && ($l->HomeDir($GetValue) =~ m!^/($|var/|run/)! || $l->LoginShell($GetValue) =~ /(nologin|false)$/); push @UserReal, $l if $l->UserName($GetValue) =~ /^\Q$strfilt/; } my $i; foreach my $l (@UserReal) { $i++; my $uid = $l->Uid($GetValue); if (!defined $uid) { warn "bogus user at line $i\n"; next; } my $a = $l->Gid($GetValue); $group = $ctx->LookupGroupById($a); $groupnm = ''; $expr = ComputeLockExpire($l); $group and $groupnm = $group->GroupName($GetValue); my $s = $l->Gecos($GetValue); c::set_tagged_utf8($s); $utree_model->append_set([ 0 => $l->UserName($GetValue), 1 => $l->Uid($GetValue), 2 => $groupnm, 3 => $s, 4 => $l->LoginShell($GetValue), 5 => $l->HomeDir($GetValue), 6 => $expr ]); } } sub RefreshGroupsFull { my ($filtergroups, $strfilt) = @_; my $groups; defined $ctx and $groups = $ctx->GroupsEnumerateFull; $gtree_model->clear; my @GroupReal; LOOP: foreach my $g (@$groups) { next LOOP if $filtergroups && $g->Gid($GetValue) <= 499 || $g->Gid($GetValue) == 65534; push @GroupReal, $g if $g->GroupName($GetValue) =~ /^\Q$strfilt/; } foreach my $g (@GroupReal) { my $a = $g->GroupName($GetValue); #my $group = $ctx->LookupGroupById($a); my $u_b_g = $a && $ctx->EnumerateUsersByGroup($a); my $listUbyG = join(',', @$u_b_g); my $group_id = $g->Gid($GetValue); $gtree_model->append_set([ 0 => $g->GroupName($GetValue), if_($group_id, 1 => $group_id), if_($listUbyG, 2 => $listUbyG) ]); } } sub Refresh { my ($filt, $strfilt) = @_; RefreshUsersFull($filt, $strfilt); RefreshGroupsFull($filt, $strfilt); GrayDelEdit(); RefreshXguest(1); } sub GetFaceIcon { my ($o_user) = @_; my @icons = any::facesnames(); my $i; my $current_icon; my $user_icon = "$::prefix/usr/share/faces/$o_user.png"; gtkpack_(my $hb = Gtk3::HBox->new(0, 2), 0, Gtk3::Label->new(N("Click on the icon to change it") . ' '), 0, my $bt = Gtk3::Button->new ); my $set = sub { my ($icon) = @_; my $f = $icon =~ m,^/, ? $icon : any::face2png($icon); $us->{o}{iconval} = $icon; gtkdestroy($us->{o}{icon}); # so that we do display "no icon" if either file doesn't exist or it's corrupted: undef $us->{o}{icon}; $us->{o}{icon} = eval { gtkcreate_img($f) } if -e $f; $us->{o}{icon} ||= Gtk3::Label->new("No Icon"); $bt->add($us->{o}{icon}); $us->{o}{icon}->show; }; if (-e $user_icon) { my $current_md5 = common::md5file($user_icon); eval { $i = find_index { common::md5file(any::face2png($_)) eq $current_md5 } @icons }; if (!$@) { #- current icon found in @icons, select it $current_icon = $icons[$i]; } else { #- add and select current icon in @icons push @icons, $user_icon; $current_icon = $user_icon; $i = @icons - 1; } } else { #- no icon yet, select a random one $current_icon = $icons[$i = rand(@icons)]; } $set->($current_icon); my $leave = 0; $bt->signal_connect(enter => sub { $leave = 0 }); $bt->signal_connect(leave => sub { $leave = 1 }); $bt->signal_connect(button_release_event => sub { return if $leave; if ($_[1]->button == 1) { $i = defined $icons[$i+1] ? $i+1 : 0; } else { $i = defined $icons[$i-1] ? $i-1 : @icons-1; } $set->($icons[$i]); }); $hb; } sub AddUser() { my $w = NewWindow(N("Create New User")); my $dontcreatehomedir = 0; # Be restrictive by default, and use umask if known. my $homedir_perms = 0700; my $is_system = 0; my %u; gtkpack_($w->get_child, 0, BuildUui(), 0, Gtk3::HSeparator->new, 0, $us->{o}{createhomedir} = Gtk3::CheckButton->new(N("Create Home Directory")), 0, gtkpack_(my $vv = Gtk3::HBox->new(0, 4), 0, Gtk3::Label->new(N("Home Directory: ")), 0, $us->{o}{homedir} = Gtk3::Entry->new ), 0, $us->{o}{privategroup} = Gtk3::CheckButton->new(N("Create a private group for the user")), 0, $us->{o}{userid} = Gtk3::CheckButton->new(N("Specify user ID manually")), 0, gtkset_sensitive(my $h = Gtk3::HBox->new(0, 4), 0), 0, Gtk3::HSeparator->new, 0, GetFaceIcon() ); gtkadd($w->get_action_area, map { my $r = $_->[1]; gtksignal_connect( Gtk3::Button->new($_->[0]), clicked => sub { if (!$r) { $u{username} = $us->{o}{login}->get_text; $error = 0; $u{gecos} = $us->{o}{fullname}->get_text; if (!valid_username($u{username})) { RaiseError($us->{error}) } my $nm = !$error && $ctx->LookupUserByName($u{username}); if ($nm) { RaiseError(N("User already exists, please choose another User Name")); $us->{o}{login}->set_text(''); } $u{passwd} = $us->{o}{passwd}->get_text; if ($u{passwd} ne $us->{o}{confpasswd}->get_text) { RaiseError(N("Password Mismatch")); } if (weakPasswordForSecurityLevel($u{passwd})) { RaiseError(N("This password is too simple. \n Good passwords should be > 6 characters")); } my $userEnt = !$error && $ctx->InitUser($u{username}, $is_system); if (!$error && $us->{o}{createhomedir}->get_active) { $dontcreatehomedir = 0; $u{homedir} = $us->{o}{homedir}->get_text; $userEnt and $userEnt->HomeDir($u{homedir}); # Correctly set permissions on the home directory. if (my $umask = get_params('/etc/login.defs', qw(UMASK))->{UMASK}) { $homedir_perms = 0777 &~ oct($umask); } } else { $dontcreatehomedir = 1; } if (!$error && $us->{o}{userid}->get_active) { if (($u{uid} = $us->{o}{uid}->get_value) < 1000) { my $uidchoice = GimmeChoice(N("User Uid is < 1000"), N("Creating a user with a UID less than 1000 is not recommended.\n Are you sure you want to do this?\n\n")); $uidchoice and $userEnt->Uid($u{uid}); } else { $userEnt->Uid($u{uid}) } } if ($us->{o}{privategroup}->get_active) { if (!$error) { #Check if group exist my $gr = $ctx->LookupGroupByName($u{username}); if ($gr) { my $groupchoice = ChooseGroup(); if ($groupchoice == 0 && !$error) { #You choose to put it in the existing group $u{gid} = $gr->Gid($GetValue); } elsif ($groupchoice == 1) { # Put it in 'users' group log::explanations(N("Putting %s to 'users' group", $u{username})); $u{gid} = Add2UsersGroup($u{username}); } } else { #it's a new group: Add it my $newgroup = $ctx->InitGroup($u{username},$is_system); log::explanations(N("Creating new group: %s", $u{username})); $u{gid} = $newgroup->Gid($GetValue); $ctx->GroupAdd($newgroup); } } } else { !$error and $u{gid} = Add2UsersGroup($u{username}); } if (!$error) { log::explanations(N("Adding user: %s", $u{username})); $u{loginshell} = $us->{o}{shells}->entry->get_text; $userEnt->Gecos($u{gecos}); $userEnt->LoginShell($u{loginshell}); $userEnt->Gid($u{gid}); $userEnt->ShadowMin(-1); $userEnt->ShadowMax(99999); $userEnt->ShadowWarn(-1); $userEnt->ShadowInact(-1); if (-e $u{homedir} && -d $u{homedir}) { my $keephomedir = $in->ask_yesorno(N("Home directory already exists"), N("The requested home directory already exists. Would you like to preserve the existing directory?")); return if !$keephomedir; } $ctx->UserAdd($userEnt, $is_system, $dontcreatehomedir, $homedir_perms); $ctx->UserSetPass($userEnt, $u{passwd}); defined $us->{o}{iconval} and any::addKdmIcon($u{username}, $us->{o}{iconval}); Refresh($sysfilter, $stringsearch); transfugdrake::get_windows_disk() and $in->ask_yesorno(N("Migration wizard"), N("Do you want to run the migration wizard in order to import Windows documents and settings in your Mageia distribution?")) and run_program::raw({ detach => 1 }, 'transfugdrake'); } } !$error and $w->destroy; $error = 0 }); } ([ N("Cancel"), 1 ], [ N("Ok"), 0 ]), ); foreach (qw(privategroup createhomedir)) { $us->{o}{$_}->set_active(1) } GrayBox($us->{o}{createhomedir}, $vv, 1); $us->{o}{login}->signal_connect( 'focus_out_event' => sub { my $fullname = $us->{o}{fullname}->get_text; $us->{o}{homedir}->set_text("/home/" . $us->{o}{login}->get_text); $fullname or $us->{o}{fullname}->set_text($us->{o}{login}->get_text); 0; # Gdk expect focus event handlers to return false }); $us->{o}{uid} = Gtk3::SpinButton->new(Gtk3::Adjustment->new($minimum_id, 1, 65000, 1, 10, 10), 1, 0); $h->pack_end($us->{o}{uid}, 0, 0, 4); $h->pack_end(Gtk3::Label->new(N("UID: ")), 0, 0, 4); GrayBox($us->{o}{userid}, $h, 0); $w->show_all; } sub Add2UsersGroup { my $name = shift; my $usersgroup = $ctx->LookupGroupByName('users'); $usersgroup->MemberName($name, 1); return $usersgroup->Gid($GetValue); } sub ChooseGroup() { my $w = NewWindow(N("Choose group")); my $choice; my @radio = gtkradio(N("Add to the existing group"), (N("Add to the existing group"), N("Add to the 'users' group"))); gtkadd($w->get_child, Gtk3::Label->new(N("A group with this name already exists. What would you like to do?")), gtkpack(Gtk3::VBox->new(0,0), @radio), ); gtkadd($w->get_action_area, gtksignal_connect( Gtk3::Button->new(N("Ok")), clicked => sub { each_index { $_->get_active and $choice = $::i } @radio; $w->destroy; Gtk3->main_quit; }), gtksignal_connect( Gtk3::Button->new(N("Cancel")), clicked => sub { $error = 1; $w->destroy; Gtk3->main_quit; })); $w->show_all; Gtk3->main; $choice; } sub GimmeChoice { my ($title, $text) = @_; my $choice = $in->ask_yesorno($title, $text) or $error = 1; gtkset_mousecursor_normal(); $choice; } sub AddGroup() { my $w = NewWindow(N("Create New Group")); my $mode = 0; my %g; my $is_system = 0; gtkpack_($w->get_child, 0, BuildGui(), 0, Gtk3::HSeparator->new, 0, $us->{o}{groupid} = Gtk3::CheckButton->new(N("Specify group ID manually")), 0, gtkset_sensitive(my $h = Gtk3::HBox->new(0, 4), 0), ); gtkadd($w->get_action_area, map { my $r = $_->[1]; gtksignal_connect( Gtk3::Button->new($_->[0]), clicked => sub { if (!$r) { $g{groupname} = $us->{o}{groupname}->get_text; $error = 0; if (!valid_groupname($g{groupname})) { RaiseError($us->{error}); } my $nm = $ctx->LookupGroupByName($g{groupname}); if ($nm) { RaiseError(N("Group already exists, please choose another Group Name")); $us->{o}{groupname}->set_text(''); } my $groupEnt = $ctx->InitGroup($g{groupname}, $is_system); if ($us->{o}{groupid}->get_active) { if (($g{gid} = $us->{o}{gid}->get_value) < 1000) { my $gidchoice = GimmeChoice(N(" Group Gid is < 1000"), N("Creating a group with a GID less than 1000 is not recommended.\n Are you sure you want to do this?\n\n")); $gidchoice and $groupEnt->Gid($g{gid}); } else { $groupEnt->Gid($g{gid}); } } if (!$error) { log::explanations(N("Adding group: %s ", $g{groupname})); $ctx->GroupAdd($groupEnt); Refresh($sysfilter, $stringsearch); } } !$error and $w->destroy; $error = 0 }); } ([ N("Cancel"), 1 ], [ N("Ok"), 0 ]) ); $us->{o}{gid} = Gtk3::SpinButton->new(Gtk3::Adjustment->new($minimum_id, 1, 65000, 1, 10, 10), 1, 0); $h->pack_end($us->{o}{gid}, 0, 0, 4); $h->pack_end(Gtk3::Label->new(N("GID: ")), 0, 0, 4); $us->{o}{groupid}->signal_connect('clicked' => sub { $mode = !$mode; $h->set_sensitive($mode) }); $w->show_all; } sub UpdateOrDelUsersInGroup { my ($name, $action) = @_; my $groups = $ctx->GroupsEnumerateFull; if ($action) { foreach my $g (@$groups) { my $members = $g->MemberName(1, 0); if (InArray($name, $members)) { eval { $g->MemberName($name, 2) }; eval { $ctx->GroupModify($g) }; } } } } sub GetNameEntFromIter { my ($tree, $model, $rank) = @_; my (undef, $iter) = $tree->get_selection->get_selected; my $name = $model->get($iter, $rank); $name; } sub FillUserInfo { my $ent = shift; my $s = $ent->Gecos($GetValue); c::set_tagged_utf8($s); $us->{o}{fullname}->set_text($s); $us->{o}{passwd}->set_text(' '); $us->{o}{confpasswd}->set_text(' '); $us->{o}{shells}->entry->set_text($ent->LoginShell($GetValue)); $us->{o}{homedir}->set_text($ent->HomeDir($GetValue)); } sub UserDelete() { my ($checkhome, $checkspool); my $username = GetNameEntFromIter($usertree, $utree_model, 0); my $userEnt = $ctx->LookupUserByName($username); # Old Delete version #my $removehome = GimmeChoice(N(" Remove Home Directory"), N("Do you want to delete the user's home directory and mail spool?")); #$removehome and $ctx->Clean($userEnt); # New version my $w = NewWindow(N("Delete files or not?")); my $hd = $userEnt->HomeDir($GetValue); gtkpack_($w->get_child, 0, Gtk3::Label->new(N("Deleting user %s\n Also perform the following actions\n", $username)), 0, $checkhome = Gtk3::CheckButton->new(N("Delete Home Directory: %s", $hd)), 0, $checkspool = Gtk3::CheckButton->new(N("Delete Mailbox: /var/spool/mail/%s", $username)), ); gtkadd($w->get_action_area, map { my $r = $_->[1]; gtksignal_connect(Gtk3::Button->new($_->[0]), clicked => sub { if (!$r) { log::explanations(N("Removing user: %s", $username)); $ctx->UserDel($userEnt); UpdateOrDelUsersInGroup($username, 1); #Let's check out the user's primary group my $usergid = $userEnt->Gid($GetValue); my $groupEnt = $ctx->LookupGroupById($usergid); if ($groupEnt) { my $member = $groupEnt->MemberName(1, 0); if (scalar(@$member) == 0 && $groupEnt->Gid($GetValue) > 499) { $ctx->GroupDel($groupEnt); } } if ($checkhome->get_active) { eval { $ctx->CleanHome($userEnt) }; $@ and RaiseError($@); } if ($checkspool->get_active) { eval { $ctx->CleanSpool($userEnt) }; $@ and RaiseError($@); } Refresh($sysfilter, $stringsearch); } !$error and $w->destroy; $error = 0 }); } ([ N("Cancel"), 1 ], [ N("Delete"), 0 ]), ); if ($hd !~ m!(?:/home|/var/spool)!) { $checkhome->set_sensitive(0); $checkspool->set_sensitive(0) } $w->show_all; } sub GroupDelete() { my $groupname = GetNameEntFromIter($grouptree, $gtree_model, 0); my $wg = NewWindow(translate("Warning")); gtkadd($wg->get_child, Gtk3::Label->new(N("Do you really want to delete the group %s?", $groupname)), ); gtkadd($wg->get_action_area, map { my $r = $_->[1]; gtksignal_connect(Gtk3::Button->new($_->[0]), clicked => sub { if (!$r) { my $groupEnt = $ctx->LookupGroupByName($groupname); my $members = $ctx->EnumerateUsersByGroup($groupname); GLOOP: foreach my $username (@$members) { my $userEnt = $ctx->LookupUserByName($username); if ($userEnt && $userEnt->Gid($GetValue) == $groupEnt->Gid($GetValue)) { RaiseError(N("%s is a primary group for user %s\n Remove the user first", $groupname, $username)); last GLOOP; } } if (!$error) { log::explanations(N("Removing group: %s", $groupname)); eval { $ctx->GroupDel($groupEnt) }; Refresh($sysfilter, $stringsearch); } } !$error and $wg->destroy; $error = 0 }); } ([ N("Cancel"), 1 ], [ N("Delete"), 0 ]), ); $wg->show_all; } sub Delete() { my $page = $nb->get_current_page; $us->{wnd}{rwindow}->set_sensitive(0); gtkset_mousecursor_wait(); $error = 0; if ($page <= 0) { UserDelete(); } elsif ($page == 1) { GroupDelete(); } $us->{wnd}{rwindow}->set_sensitive(1); gtkset_mousecursor_normal(); } # Gtk Facilities sub CreateTree { my ($tree_model) = @_; my $tree = Gtk3::TreeView->new_with_model($tree_model); $tree->get_selection->set_mode('browse'); $tree->set_headers_visible(1); $tree->set_rules_hint(1); $tree->get_selection->signal_connect( 'changed' => sub { foreach ($tbedit, $tbdel, $buttorcheck{edit}, $buttorcheck{delete}) { $_->set_sensitive(1); } }); my $menu_treeview = Gtk3::Menu->new; my @menu_treeview_actions = ([ 'edit', N("Edit") ], [ 'delete', N("Delete") ]); foreach (@menu_treeview_actions) { my ($action, $text) = @$_; my %actions; %actions = ( edit => sub { my (undef, $iter) = $tree->get_selection->get_selected; $iter and Edit(); }, delete => sub { my (undef, $iter) = $tree->get_selection->get_selected; $iter and Delete(); } ); $menu_treeview->append( gtksignal_connect( gtkshow(Gtk3::MenuItem->new_with_label($text)), activate => sub { $actions{$action}->() })); } $tree->signal_connect( button_press_event => sub { my (undef, $event) = @_; my (undef, $iter) = $tree->get_selection->get_selected; return unless $iter; foreach ($tbedit, $tbdel, $buttorcheck{edit}, $buttorcheck{delete}) { $_->set_sensitive(1); } Edit() if $event->type eq '2button-press'; $_[1]->button == 3 and $menu_treeview->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time); }); $tree->signal_connect( key_press_event => sub { my (undef, $event) = @_; my (undef, $iter) = $tree->get_selection->get_selected; return unless $iter; Edit() if $event->keyval == Gtk3::Gdk::KEY_Return; }); $tree; } sub GtkEntryHidePass { my ($o_text) = @_; my $e = gtknew('WeaknessCheckEntry'); $o_text and $e->set_text($o_text); $e->set_visibility(0); $e; } sub GtkEntrySized { my ($i, $spac) = @_; my $e = Gtk3::Entry->new_with_max_length($i); $e->set_size_request($spac, 20); $e; } sub BuildUui { my ($o_extra_widget) = @_; gtkpack_(my $vbox = Gtk3::VBox->new(0, 2), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings => 5 }, [ gtknew('Label_Left', text => N("Full Name:")), $us->{o}{fullname} = Gtk3::Entry->new ], [ gtknew('Label_Left', text => N("Login:")), $us->{o}{login} = Gtk3::Entry->new ], [ gtknew('Label_Left', text => N("Password:")), $us->{o}{passwd} = GtkEntryHidePass() ], [ gtknew('Label_Left', text => N("Confirm Password:")), $us->{o}{confpasswd} = GtkEntryHidePass() ], [ gtknew('Label_Left', text => N("Login Shell:")), $us->{o}{shells} = Gtk3::ComboBoxText->new ], $o_extra_widget, ) ); $us->{o}{shells}->set_popdown_strings(@{$ctx->GetUserShells}); $us->{o}{shells}->entry->set_text("/bin/bash"); $vbox; } sub BuildGui() { gtkpack_(my $vbox = Gtk3::VBox->new(0, 2), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings =>5 }, [ N("Group Name:"), $us->{o}{groupname} = Gtk3::Entry->new ] ) ); $vbox; } sub NewWindow { my ($title) = @_; my $dialog = gtkset_border_width( _create_dialog($title, { transient_for => $us->{wnd}{real_window} }), 5); $dialog->signal_connect(delete_event => sub { $dialog->destroy }); my $prev_main_window = $::main_window; $::main_window = $dialog; $dialog->signal_connect(destroy => sub { $::main_window = $prev_main_window }); $dialog; } sub Xguest() { if (is_xguest_installed()) { run_program::run('rpm', '-e', 'xguest'); } else { run_program::run('gurpmi2', 'xguest'); } RefreshXguest(); } sub UserEdit_valid { my ($userEnt, $model, $primgid) = @_; my ($Exp, $gEnt, $ugid); my $error = 0; my %u = ( username => $us->{o}{login}->get_text, gecos => $us->{o}{fullname}->get_text, homedir => $us->{o}{homedir}->get_text, pw => $us->{o}{passwd}->get_text, confm => $us->{o}{confpasswd}->get_text, shell => $us->{o}{shells}->entry->get_text ); if (!valid_username($u{username})) { RaiseError($us->{error}); } if ($u{pw} ne $u{confm}) { RaiseError(N("Password Mismatch")); } elsif ($u{pw} eq $u{confm} && $u{pw} ne ' ') { if (weakPasswordForSecurityLevel($u{pw})) { RaiseError(N("This password is too simple. \n Good passwords should be > 6 characters")); } !$error and $ctx->UserSetPass($userEnt, $u{pw}); } if (!$error) { $userEnt->UserName($u{username}); $userEnt->Gecos($u{gecos}); $userEnt->HomeDir($u{homedir}); $userEnt->LoginShell($u{shell}); my $username = $userEnt->UserName($GetValue); $model->foreach(sub { my ($mod, $_path, $iter) = @_; my $ch = $mod->get($iter, 2); my $name = $mod->get($iter, 1); $gEnt = $ctx->LookupGroupByName($name); $ugid = $gEnt->Gid($GetValue); my $m = $gEnt->MemberName(1,0); if ($ch == 1) { if (!InArray($username, $m) && $primgid != $ugid) { eval { $gEnt->MemberName($username, 1) }; $ctx->GroupModify($gEnt); } } else { if (InArray($username, $m)) { eval { $gEnt->MemberName($username, 2) }; $ctx->GroupModify($gEnt); } } return 0; }, undef); if ($us->{o}{primgroup}->entry->get_text eq '') { RaiseError(N("Please select at least one group for the user")); } elsif (!$error) { my $ent = $ctx->LookupGroupByName($us->{o}{primgroup}->entry->get_text); $ugid = $ent->Gid($GetValue); $userEnt->Gid($ugid); if ($us->{o}{acheckexpire}->get_active) { my $yr = $us->{o}{expy}->get_value; my $mo = $us->{o}{expm}->get_value; my $dy = $us->{o}{expd}->get_value; ValidInt($yr, $dy, $mo) or RaiseError(N("Please specify Year, Month and Day \n for Account Expiration ")); if (!$error) { $Exp = ConvTime($dy, $mo, $yr); $userEnt->ShadowExpire($Exp) } } else { $userEnt->ShadowExpire(ceil(-1)) } if ($us->{o}{pcheckexpire}->get_active) { my $allowed = int($us->{o}{dbca}->get_text); my $required = int($us->{o}{dbcr}->get_text); my $warning = int($us->{o}{bwbc}->get_text); my $inactive = int($us->{o}{dbai}->get_text); $allowed && $required && $warning && $inactive or RaiseError(N("Please fill up all fields in password aging\n")); if (!$error) { $userEnt->ShadowMin($allowed); $userEnt->ShadowMax($required); $userEnt->ShadowWarn($warning); $userEnt->ShadowInact($inactive); } } else { $userEnt->ShadowMin(-1); $userEnt->ShadowMax(99999); $userEnt->ShadowWarn(-1); $userEnt->ShadowInact(-1); } !$error and $ctx->UserModify($userEnt); if ($us->{o}{lockuser}->get_active) { !$ctx->IsLocked($userEnt) and $ctx->Lock($userEnt); } else { $ctx->IsLocked($userEnt) and $ctx->UnLock($userEnt) } defined $us->{o}{iconval} and any::addKdmIcon($u{username}, $us->{o}{iconval}); !$error and Refresh($sysfilter, $stringsearch); } } } sub GroupEdit_valid { my ($groupEnt, $model, $groupname) = @_; my %g; my ($uEnt); $g{groupname} = $us->{o}{groupname}->get_text; $error = 0; if (!valid_groupname($g{groupname})) { RaiseError($us->{error}) } if (!$error && $groupname ne $g{groupname}) { $groupEnt->GroupName($g{groupname}) } $groupname = $groupEnt->GroupName($GetValue); my $members = $ctx->EnumerateUsersByGroup($groupname); my $gid = $groupEnt->Gid($GetValue); !$error and $model->foreach( sub { my ($mod, $_path, $iter) = @_; my $ch = $mod->get($iter, 2); my $name = $mod->get($iter, 1); if ($ch == 1) { if (!InArray($name, $members)) { $uEnt = $ctx->LookupUserByName($name); my $ugid = $uEnt->Gid($GetValue); if ($ugid != $gid) { eval { $groupEnt->MemberName($name,1) }; } } } else { if (InArray($name, $members)) { $uEnt = $ctx->LookupUserByName($name); if ($uEnt->Gid($GetValue) == $groupEnt->Gid($GetValue)) { $model->set($iter, 0 => gtkcreate_pixbuf($pix[0])); $model->set($iter, 2 => 1); RaiseError(N("You cannot remove user '%s' from their primary group", $name)); } if (!$error) { eval { $groupEnt->MemberName($name,2) }; } } } return 0; }, undef); if (!$error) { $ctx->GroupModify($groupEnt); Refresh($sysfilter, $stringsearch); } } sub UserEdit_widget { my ($nbU, $tree, $model, $Gent) = @_; my ($vald, $mo, $ye) = (localtime())[3, 4, 5]; my $valy = $ye+1900; my $valm = $mo+1; gtkappend_page($nbU, BuildUui([ gtknew('Label_Left', text => N("Home:")), $us->{o}{homedir} = Gtk3::Entry->new ]), gtkshow(Gtk3::Label->new(N("User Data")))); gtkappend_page( $nbU, gtkpack_(Gtk3::VBox->new(0, 2), 0, $us->{o}{acheckexpire} = Gtk3::CheckButton->new(N("Enable account expiration")), 0, gtkpack__(my $h = Gtk3::HBox->new(0, 10), Gtk3::Label->new(N("Account expires (YYYY-MM-DD):")), map { my ($s, $value, $minv, $maxv) = @$_; #my $spc = $_->[4]; $us->{o}{$s} = Gtk3::SpinButton->new(Gtk3::Adjustment->new($value, $minv , $maxv, 1, 10, 10), 1, 0); } (['expy', $valy, 1970, 10000], ['expm', $valm, 1, 12], ['expd', $vald, 1, 31]), ), 0, Gtk3::HSeparator->new, 0, $us->{o}{lockuser} = Gtk3::CheckButton->new(N("Lock User Account")), 0, Gtk3::HSeparator->new, 0, GetFaceIcon(GetNameEntFromIter($usertree, $utree_model,0)) ), gtkshow(Gtk3::Label->new(N("Account Info")))); GrayBox($us->{o}{acheckexpire}, $h, 0); gtkappend_page($nbU, gtkpack_(Gtk3::VBox->new(0, 2), 0, gtkpack_(Gtk3::HBox->new(0,5), 0, Gtk3::Label->new(N("User last changed password on: ")), 0, my $dayStr = Gtk3::Label->new(""), 0, my $month = Gtk3::Label->new(""), 0, my $dayInt = Gtk3::Label->new(""), 0, my $year = Gtk3::Label->new("") ), 0, Gtk3::HSeparator->new, 0, $us->{o}{pcheckexpire} = Gtk3::CheckButton->new(N("Enable Password Expiration")), 1, gtkpack_( my $v = Gtk3::VBox->new(0,1), 1, create_packtable( { homogeneous => 1, col_spacings => 5, row_spacings => 5 }, [ N("Days before change allowed:"), $us->{o}{dbca} = Gtk3::Entry->new_with_text(0) ], [ N("Days before change required:"), $us->{o}{dbcr} = Gtk3::Entry->new_with_text(0) ], [ N("Days warning before change:"), $us->{o}{bwbc} = Gtk3::Entry->new_with_text(0) ], [ N("Days before account inactive:"), $us->{o}{dbai} = Gtk3::Entry->new_with_text(0) ] ) )), gtkshow(Gtk3::Label->new(N("Password Info")))); GrayBox($us->{o}{pcheckexpire}, $v, 0); gtkappend_page($nbU, gtkpack_(Gtk3::VBox->new(0, 2), 0, Gtk3::Label->new(N("Select the groups that the user will be a member of:")), 1, create_scrolled_window($tree), 0, gtkpack_(Gtk3::HBox->new(0, 1), 0, Gtk3::Label->new(N("Primary Group")), 1, $us->{o}{primgroup} = Gtk3::ComboBoxText->new, ) ), gtkshow(Gtk3::Label->new(N("Groups")))); my $username = GetNameEntFromIter($usertree, $utree_model,0); $us->{o}{login}->set_text($username); my $userEnt = $ctx->LookupUserByName($username); FillUserInfo($userEnt); my $Uid = $userEnt->Uid($GetValue); my $expire = $userEnt->ShadowExpire($GetValue); if ($expire && $expire != -1) { $us->{o}{acheckexpire}->set_active(1); $h->set_sensitive(1); my $times = TimeOfArray($expire, 1); $us->{o}{expd}->set_value($times->{dayint}); $us->{o}{expm}->set_value($times->{month}); $us->{o}{expy}->set_value($times->{year}); } #root account should never be locked !$Uid and $us->{o}{lockuser}->set_sensitive(0); # Check if user account is locked $ctx->IsLocked($userEnt) and $us->{o}{lockuser}->set_active(1); my $lastchg = $userEnt->ShadowLastChange($GetValue); if ($lastchg) { my $times = TimeOfArray($lastchg, 0); $dayStr->set_text($times->{daystr}); $month->set_text($times->{month}); $dayInt->set_text($times->{dayint}); $year->set_text($times->{year}); } my $min = $userEnt->ShadowMin($GetValue); my $max = $userEnt->ShadowMax($GetValue); my $warn = $userEnt->ShadowWarn($GetValue); my $inact = $userEnt->ShadowInact($GetValue); if ($min && $min != -1 || $max && $max != 99999 || $warn && $warn != 7 && $warn != -1 || $inact && $inact != -1) { $us->{o}{pcheckexpire}->set_active(1); $v->set_sensitive(1); } $min && $min != -1 and $us->{o}{dbca}->set_text($min); $max && $max != -1 and $us->{o}{dbcr}->set_text($max); $warn && $warn != -1 and $us->{o}{bwbc}->set_text($warn); $inact && $inact != -1 and $us->{o}{dbai}->set_text($inact); my $grps = $ctx->GroupsEnumerate; my @sgroups = sort @$grps; my $members = $ctx->EnumerateGroupsByUser($username); my $primgid = $userEnt->Gid($GetValue); $Gent = $ctx->LookupGroupById($primgid); my @primgroup; foreach my $group (@sgroups) { if (member($group, @$members)) { $model->append_set([ 0 => gtkcreate_pixbuf($pix[0]), 1 => $group, 2 => 1 ]); push @primgroup, $group; } else { $model->append_set([ 0 => gtkcreate_pixbuf($pix[1]), 1 => $group, 2 => 0 ]) } } $us->{o}{primgroup}->set_popdown_strings(@primgroup); $Gent and $us->{o}{primgroup}->entry->set_text($Gent->GroupName($GetValue)); ($userEnt, $primgid, @primgroup); } sub GroupEdit_widget { my ($nbG, $tree, $model, $groupname) = @_; $nbG->set_size_request(300, 200); gtkappend_page($nbG, gtkpack_(Gtk3::VBox->new(0, 2), 1, BuildGui()), gtkshow(Gtk3::Label->new(N("Group Data")))); gtkappend_page($nbG, gtkpack_(Gtk3::VBox->new(0, 1), 0, Gtk3::Label->new(N("Select the users to join this group:")), 1, create_scrolled_window($tree)), gtkshow(Gtk3::Label->new(N("Group Users")))); $groupname = GetNameEntFromIter($grouptree, $gtree_model, 0); $us->{o}{groupname}->set_text($groupname); # Don't allow change on group name since there is a bug in lu_user_modify group $us->{o}{groupname}->set_editable(0); my $groupEnt = $ctx->LookupGroupByName($groupname); my $users = $ctx->UsersEnumerate; my @susers = sort(@$users); my $members = $ctx->EnumerateUsersByGroup($groupname); foreach my $user (@susers) { if (member($user, @$members)) { $model->append_set([ 0 => gtkcreate_pixbuf($pix[0]), 1 => $user, 2 => 1 ]); } else { $model->append_set([ 0 => gtkcreate_pixbuf($pix[1]), 1 => $user, 2 => 0 ]); } } $groupEnt; } sub Edit() { my $w = NewWindow(N("Edit Groups / Users")); my $model = Gtk3::ListStore->new("Gtk3::Gdk::Pixbuf", "Glib::String", "Glib::Int"); my $tree = Gtk3::TreeView->new_with_model($model); my ($groupname, $groupEnt, $userEnt, $primgid, $Gent); my @primgroup; $tree->get_selection->set_mode('browse'); my $check = Gtk3::TreeViewColumn->new_with_attributes("", Gtk3::CellRendererPixbuf->new, 'pixbuf' => 0); $tree->append_column($check); $check->{is_pixbuf_column} = 1; $tree->append_column(Gtk3::TreeViewColumn->new_with_attributes("", Gtk3::CellRendererText->new, 'text' => 1)); $tree->set_headers_visible(0); my $p = $nb->get_current_page; my $nbU = Gtk3::Notebook->new; my $nbG = Gtk3::Notebook->new; if ($p <= 0) { ($userEnt, $primgid, @primgroup) = UserEdit_widget($nbU, $tree, $model, $Gent); } elsif ($p == 1) { $groupEnt = GroupEdit_widget($nbG, $tree, $model, $groupname); } gtkpack_($w->get_child, 1, !$p ? $nbU : $nbG); gtkadd($w->get_action_area, map { my $retv = $_->[1]; gtksignal_connect( Gtk3::Button->new($_->[0]), clicked => sub { if (!$retv) { if ($p <= 0) { eval { UserEdit_valid($userEnt, $model, $primgid) }; if (my $err = $@) { c::set_tagged_utf8($err); RaiseError(N("An error occurred:") . "\n" . $err); } } elsif ($p == 1) { GroupEdit_valid($groupEnt, $model, $groupname); } } !$error and $w->destroy; $error = 0 }); } ([ N("Cancel"), 1 ], [ N("Ok"), 0 ]), ); $tree->show; $w->show_all; $tree->signal_connect( button_press_event => sub { return if $_[1] =~ /Gtk3::Gdk::Event=/; my ($path, $column) = $tree->get_path_at_pos($_[1]->x, $_[1]->y); if ($path && $column) { if ($column->{is_pixbuf_column}) { my $iter = $model->get_iter($path); if ($iter) { my $cp = $model->get($iter, 2); my $item = $model->get($iter, 1); $model->set($iter, 0 => gtkcreate_pixbuf($pix[$cp])); $model->set($iter, 2 => !$cp); if ($p <= 0) { if (!$cp) { !InArray($item, \@primgroup) and push(@primgroup, $item); } else { InArray($item, \@primgroup) and @primgroup = RemoveFromArray($item, \@primgroup); } my $old = $us->{o}{primgroup}->entry->get_text; $us->{o}{primgroup}->set_popdown_strings(@primgroup); $us->{o}{primgroup}->entry->set_text($old); } } } } }); $tree->signal_connect( key_press_event => sub { my $c = chr($_[1]->keyval & 0xff); if ($_[1]->keyval >= 0x100 ? $c eq "\r" || $c eq "\x8d" : $c eq ' ') { my (undef, $iter) = $tree->get_selection->get_selected; return unless $iter; my $cp = $model->get($iter, 2); my $item = $model->get($iter, 1); $model->set($iter, 0 => gtkcreate_pixbuf($pix[$cp])); $model->set($iter, 2 => !$cp); if ($p <= 0) { if (!$cp) { !InArray($item, \@primgroup) and push(@primgroup, $item); } else { InArray($item, \@primgroup) and @primgroup = RemoveFromArray($item, \@primgroup); } my $old = $us->{o}{primgroup}->entry->get_text; $us->{o}{primgroup}->set_popdown_strings(@primgroup); $us->{o}{primgroup}->entry->set_text($old); } } 0; }); } sub ValidInt { foreach my $i (@_) { $i =~ /\d+/ or return 0 } return 1; } sub ConvTime { my ($day, $month, $year) = @_; my ($tm, $days, $mon, $yr); $mon = $month - 1; $yr = $year - 1900; $tm = POSIX::mktime(0, 0, 0, $day, $mon, $yr); $days = ceil($tm / (24 * 60 * 60)); return $days; } sub TimeOfArray { my ($reltime, $cm) = @_; my $h; my %mth = (Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6, Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12); my $_t = localtime($reltime * 24 * 60 * 60) =~ /(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\d+)/; $h->{daystr} = $1; $h->{month} = $2; $h->{dayint} = $3; $h->{year} = $5; $cm and $h->{month} = $mth{$2}; $h; } sub InArray { my ($item, $arr) = @_; return any { $item eq $_ } @$arr; } sub RemoveFromArray { my ($item, $arr) = @_; my ($t, $_s) = partition { $item ne $_ } @$arr; return @$t; } sub GrayBox { my ($o, $v, $m) = @_; $v->set_sensitive($m); $o->signal_connect('clicked' => sub { $m = !$m; $v->set_sensitive($m) }); } sub NewDialog { my ($title) = @_; my $dialog = gtkset_border_width( _create_dialog($title, { transient_for => $us->{wnd}{real_window} }), 5); $dialog->get_action_area->pack_start( gtksignal_connect(Gtk3::Button->new(N("Close")), clicked => sub { $dialog->destroy }), 0,0,0); $dialog; } sub About() { my $license = formatAlaTeX(translate($::license)); $license =~ s/\n/\n\n/sg; # nicer formatting my $w = gtknew('AboutDialog', name => N("Userdrake"), version => $us->{VERSION}, copyright => N("Copyright (C) %s by Mandriva", '2001-2009') . "\n" . N("Copyright (C) %s by Mageia", '2011'), if_(-r "$pixdir/userdrake.png", logo => "$pixdir/userdrake.png"), license => $license, wrap_license => 1, comments => N("Users Management"), transient_for => $::main_window, website => 'http://www.mageia.org', website_label => N("Mageia"), authors => [ 'Daouda Lo', 'Thierry Vignaud ' ], translator_credits => #-PO: put here name(s) and email(s) of translator(s) (eg: "John Smith ") N("_: Translator(s) name(s) & email(s)\n"), ); $w->show_all; $w->run; } sub valid { $_[0] or $us->{error} = N("Name field is empty please provide a name"), return 0; $_[0] =~ /^[a-z]+?[a-z0-9_-]*?$/ or do { $us->{error} = N("The name must contain only lower cased latin letters, numbers, `-' and `_'"); return 0; }; length($_[0]) <= $_[1] or $us->{error} = N("Name is too long"), return 0; return 1; } sub valid_username { return valid($_[0], 32); } sub valid_groupname { return valid($_[0], 16); } sub RaiseError { my $w = NewWindow(N("Error")); $error = 1; gtkpack($w->get_child, Gtk3::Label->new($_[0])); gtkadd($w->get_action_area, gtksignal_connect(Gtk3::Button->new(N("Ok")), clicked => sub { $w->destroy }) ); $w->show_all; } sub QuitGlobal() { setVarsInSh($conffile, { FILTER => bool2text($sysfilter), }); Gtk3->main_quit; }