#!/usr/bin/perl ################################################################################ # # # # # Copyright (C) 2003 MandrakeSoft # # # Daouda Lo # # # # 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); use standalone; use common; use any; # i18n: IMPORTANT: to get correct namespace (userdrake instead of libDrakX) BEGIN { unshift @::textdomains, 'userdrake' } use ugtk2 qw(:all); use interactive; use POSIX qw(mktime ceil); use USER; #Only for Debugging #use Devel::Peek; use Gtk2::Gdk::Keysyms; use log; my $conffile = '/etc/sysconfig/userdrake'; my $secfile = '/etc/sysconfig/msec'; my $pixdir = '/usr/share/userdrake/pixmaps/'; my $in = interactive->vnew('su'); my $us = {} ; $us->{VERSION} = '0.92'; my $window_splash = Gtk2::Window->new('popup'); $window_splash->signal_connect(delete_event => \&quit_global); $window_splash->set_title(N("Userdrake") . $us->{VERSION}); $window_splash->set_position('center_always'); $window_splash->add(gtkadd(gtkset_shadow_type(Gtk2::Frame->new, 'etched_out'), gtkpack(Gtk2::VBox->new(0, 0), gtkcreate_img("$pixdir/about.png"), Gtk2::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 %sec = getVarsFromSh($secfile); my $sysfilter = text2bool($prefs{FILTER}); sub HelpSystem { exec("drakhelp --id userdrake") unless fork(); }; $us->{wnd} = ugtk2->new(N("Mandrake Linux Users Management Tool") . " " . $us->{VERSION}); gtkset_size_request($us->{wnd}{rwindow}, 660, 460); $us->{wnd}{rwindow}->set_position('center'); $us->{wnd}{window}->signal_connect(delete_event => \&QuitGlobal); my $utree_model = Gtk2::ListStore->new("Glib::String", "Glib::Int", "Glib::String", "Glib::String", "Glib::String", "Glib::String", "Glib::String" ); my $gtree_model = Gtk2::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 %options = ( 'edit' => [ N("/_Actions"), N("/_Edit") ], 'delete' => [ N("/_Actions"), N("/_Delete") ], 'filter' => [ N("/_Options"), N("/_Filter system users") ] ); my %buttorcheck; my ($menu, $factory) = create_factory_menu($::isEmbedded ? $::Plug : $us->{wnd}{rwindow}, ([ N("/_File"), undef, undef, undef, '' ], [ N("/_File") . N("/_Refresh"), undef, sub { Refresh($sysfilter, $stringsearch)}, undef, '', 'gtk-refresh' ], [ N("/_File") . N("/_Quit"), N("Q"), \&QuitGlobal, undef, '', 'gtk-quit' ], [ N("/_Actions"), undef, undef, undef, '' ], [ N("/_Actions") . N("/_Add User"), undef, \&AddUser, undef, '', 'gtk-add' ], [ N("/_Actions") . N("/Add _Group"), undef, \&AddGroup, undef, '', 'gtk-add' ], [ join('', @{$options{edit}}), undef, \&Edit, undef, '', 'gtk-properties' ], [ join('', @{$options{delete}}), undef, \&Delete, undef, '', 'gtk-delete' ], [ N("/_Options"), undef, undef, undef, '' ], [ join('', @{$options{filter}}), undef, sub { $sysfilter = $buttorcheck{filter}->get_active; Refresh($sysfilter, $stringsearch) }, undef, '' ], [ N("/_Help"), undef, undef, undef, '' ], [ N("/_Help").N("/_Help"), undef, sub { HelpSystem() }, undef, '', 'gtk-help' ], [ N("/_Help").N("/_Report Bug"), undef, sub { system("$ENV{BROWSER} https://qa.mandrakesoft.com &") }, undef, '', 'gtk-stop' ], [ N("/_Help").N("/_About..."), undef, \&About, undef, '', 'gtk-preferences' ] ) ); %buttorcheck = map { $_ => $factory->get_widget("
" . join '', map { s/_//; $_ } @{$options{$_}}) }('edit', 'delete', 'filter'); if (defined $buttorcheck{filter}) { $buttorcheck{filter}->set_active($sysfilter); } else { print STDERR "BUG with LANGUAGE $ENV{LANGUAGE}\n"; } my $toolb = Gtk2::Toolbar->new; my $filter; my $searchBox = gtkpack_(Gtk2::HBox->new(0,5), 1, Gtk2::Label->new(""), 0, Gtk2::Label->new(N("Search:")), 0, gtksignal_connect($filter = Gtk2::Entry->new, key_press_event => sub { $_[1]->keyval == $Gtk2::Gdk::Keysyms{Return} and Refresh($sysfilter, $filter->get_text()) } ), 0, my $fbut = Gtk2::Button->new(N("Apply filter")), ); gtkappend_page(my $nb = Gtk2::Notebook->new, gtkpack(create_scrolled_window($usertree)), gtkshow(Gtk2::Label->new(N("Users")))); #PO: list of users belonging to that group gtkappend_page($nb, gtkpack(create_scrolled_window($grouptree)), gtkshow(Gtk2::Label->new(N("Groups")))); $nb->set_show_border(0); $us->{wnd}{window}->add( gtkpack_(Gtk2::VBox->new(0, 0), 0, $menu, 0, $toolb, 0, $searchBox, 0, Gtk2::HSeparator->new, 1, $nb )); my @ucolsize = (60, 45, 40, 120, 80, 120, 50, -1); my @gcolsize =(100, 80, 160, -1) ; each_index { my $col = Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::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 = Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::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; map { my $t = $toolb->append_item($_->[0], $_->[1], $_->[2], Gtk2::Image->new_from_file($pixdir . $_->[2] . '.png'), $_->[3], $toolb); push(@toolbwg, $t); # $toolb->append_space; } ( [ 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 ($tbuser, $tbgroup, $tbedit, $tbdel, $tbref) = @toolbwg; GrayDelEdit(); 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; gtkset_mousecursor_normal(); Gtk2->main; ugtk2->exit; sub GrayDelEdit { foreach (($tbedit, $tbdel, $buttorcheck{edit}, $buttorcheck{delete})) { defined $_ and $_->set_sensitive(0) }; } sub TreeUnselect { my $treev = shift; $treev->get_selection->unselect_all; GrayDelEdit() } sub NotebookSwitch { my $page = $nb->get_current_page(); TreeUnselect($usertree) ; TreeUnselect($grouptree); } sub ComputeLockExpire { my $l = shift; 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, $gid, $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) ; push(@UserReal, $l) if $l->UserName($GetValue) =~ /^\Q$strfilt/; }; foreach my $l (@UserReal) { $a = $l->Gid($GetValue); $group = $ctx->LookupGroupById($a); $groupnm = ''; $expr = ComputeLockExpire($l); $group and $groupnm = $group->GroupName($GetValue); $utree_model->append_set([ 0 => $l->UserName($GetValue), 1 => $l->Uid($GetValue), 2 => $groupnm, 3 => $l->Gecos($GetValue), 4 => $l->LoginShell($GetValue), 5 => $l->HomeDir($GetValue), 6 => $expr]); } } sub RefreshGroupsFull { my ($filtergroups, $strfilt) = @_; defined $ctx and my $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->Gid($GetValue); my $group = $ctx->LookupGroupById($a); my $u_b_g = $ctx->EnumerateUsersByGroup($g->GroupName($GetValue)); 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() } sub GetFaceIcon { my $i = 0; my @m; my @icones = @m = any::facesnames(); gtkpack_(my $hb = Gtk2::HBox->new(0, 2), 0, Gtk2::Label->new(N("Click on the icon to change it"). ' '), 0, my $b = Gtk2::Button->new ); my $set = sub { gtkdestroy($us->{o}->{icon}); my $f = any::face2png($_[0]); $us->{o}->{icon} = -e $f ? gtkcreate_img($f) : Gtk2::Label->new("No Icon"); $b->add($us->{o}->{icon}); $us->{o}->{icon}->show; }; $set->($us->{o}->{iconval} = splice(@m, rand(@m), 1)); $b->signal_connect(clicked => sub { $set->($us->{o}->{iconval} = $icones[$i++]); defined $icones[$i+1] or $i = 0; }); $hb } sub AddUser { my $w = ugtk2->new(N("Create New User"), grab => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow}), if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); my $dontcreatehomedir = 0; my $is_system = 0; my %u; gtkadd($w->{window}, gtkpack_(Gtk2::VBox->new(0, 2), 0, BuildUui(), 0, Gtk2::HSeparator->new, 0, $us->{o}->{createhomedir} = Gtk2::CheckButton->new(N("Create Home Directory")), 0, gtkpack_(my $vv = Gtk2::HBox->new(0, 4), 0, Gtk2::Label->new(N("Home Directory: ")), 0, $us->{o}->{homedir} = Gtk2::Entry->new() ), 0, $us->{o}->{privategroup} = Gtk2::CheckButton->new(N("Create a private group for the user")), 0, $us->{o}->{userid} = Gtk2::CheckButton->new(N("Specify user ID manually")), 0, gtkset_sensitive(my $h = Gtk2::HBox->new(0, 4), 0), 0, Gtk2::HSeparator->new, 0, GetFaceIcon(), 0, Gtk2::HSeparator->new, 0, gtkpack(Gtk2::HBox->new(1, 20), map { my $r = $_->[1]; gtksignal_connect(Gtk2::Button->new_from_stock($_->[0]), clicked => sub { if (!$r) { $u{username} = $us->{o}->{login}->get_text(); $error = 0; if (!valid($u{username})) { RaiseError($us->{error}) }; !$error and my $nm = $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 ($sec{SECURE_LEVEL} > 3 && length($u{passwd}) < 6) { RaiseError(N("This password is too simple. \n Good passwords should be > 6 characters")) }; !$error and my $userEnt = $ctx->InitUser($u{username}, $is_system); if ($us->{o}->{createhomedir}->get_active) { $dontcreatehomedir = 0; $u{homedir} = $us->{o}->{homedir}->get_text(); $userEnt and $userEnt->HomeDir($u{homedir}); } else { $dontcreatehomedir = 1 } if ($us->{o}->{userid}->get_active) { if (($u{uid} = $us->{o}->{uid}->get_value) < 500) { my $uidchoice = GimmeChoice(N("User Uid is < 500"), N("Creating a user with a UID less than 500 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{gecos} = $us->{o}->{fullname}->get_text(); $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); $ctx->UserAdd($userEnt, $is_system, $dontcreatehomedir); $ctx->UserSetPass($userEnt, $u{passwd}); defined $us->{o}->{iconval} and any::addKdmIcon($u{username}, $us->{o}->{iconval}); Refresh($sysfilter, $stringsearch); } } !$error and Gtk2->main_quit; $error = 0; gtkset_mousecursor_normal() }) } ([ 'gtk-cancel', 1 ], [ 'gtk-ok', 0 ])), ) ); map { $us->{o}->{$_}->set_active(1) } (qw(privategroup createhomedir)); 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} = Gtk2::SpinButton->new(Gtk2::Adjustment->new(500, 1, 65000, 1, 10, 10), 1, 0); $h->pack_end($us->{o}->{uid}, 0, 0, 4); $h->pack_end(Gtk2::Label->new(N("UID: ")), 0, 0, 4); GrayBox($us->{o}->{userid}, $h, 0); $w->{rwindow}->show_all; $w->main; } sub Add2UsersGroup { my $name = shift; my $usersgroup = $ctx->LookupGroupByName('users'); $usersgroup->MemberName($name, 1); return $usersgroup->Gid($GetValue); } sub ChooseGroup() { my $w = ugtk2->new(N("Choose Group"), grab => 1, center => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); 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->{window}, gtkpack__(Gtk2::VBox->new(0,5), Gtk2::Label->new(N("A group with this name already exists. What would you like to do?")), gtkpack(Gtk2::VBox->new(0,0), @radio), Gtk2::HSeparator->new, gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new_from_stock('gtk-ok'), clicked => sub { each_index { $_->get_active and $choice = $::i } @radio; Gtk2->main_quit; }), gtksignal_connect(Gtk2::Button->new_from_stock('gtk-cancel'), clicked => sub { $w->{retval} = 0; $error = 1; Gtk2->main_quit })))); $w->main; $choice; } sub GimmeChoice { my ($title, $text) = @_; my $w = ugtk2->new($title, grab => 1, center => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); my $choice; gtkadd($w->{window}, gtkpack__(Gtk2::VBox->new(0,5), Gtk2::Label->new($text), Gtk2::HSeparator->new, gtkpack(create_hbox(), gtksignal_connect(Gtk2::Button->new(N("Yes")), clicked => sub { $choice = 1; Gtk2->main_quit; }), gtksignal_connect(Gtk2::Button->new(N("No")), clicked => sub { $choice = $w->{retval} = 0; $error = 1; Gtk2->main_quit })))); $w->main; $choice; } sub AddGroup { my $w = ugtk2->new(N("Create New Group"), grab => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); my $mode = 0; my %g; my $is_system = 0; gtkadd($w->{window}, gtkpack_(Gtk2::VBox->new(0, 2), 0, BuildGui(), 0, Gtk2::HSeparator->new, 0, $us->{o}->{groupid} = Gtk2::CheckButton->new(N("Specify group ID manually")), 0, gtkset_sensitive(my $h = Gtk2::HBox->new(0, 4), 0), 0, Gtk2::HSeparator->new, 0, gtkpack(Gtk2::HBox->new(1, 20), map { my $r = $_->[1]; gtksignal_connect(Gtk2::Button->new_from_stock($_->[0]), clicked => sub { if (!$r) { $g{groupname} = $us->{o}->{groupname}->get_text(); $error = 0; if (!valid($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) < 500) { my $gidchoice = GimmeChoice(N(" Group Gid is < 500"), N("Creating a group with a GID less than 500 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 Gtk2->main_quit; $error = 0; }) } ([ 'gtk-cancel', 1 ], [ 'gtk-ok', 0 ])), ) ); $us->{o}->{gid} = Gtk2::SpinButton->new(Gtk2::Adjustment->new(500, 1, 65000, 1, 10, 10), 1, 0); $h->pack_end($us->{o}->{gid}, 0, 0, 4); $h->pack_end(Gtk2::Label->new(N("GID: ")), 0, 0, 4); $us->{o}->{groupid}->signal_connect('clicked' => sub { $mode = !$mode; $h->set_sensitive($mode) }); $w->{rwindow}->show_all; $w->main; } 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) }; } } } else { print ("Updating MemberName \n"); } } 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; $us->{o}->{fullname}->set_text($ent->Gecos($GetValue)); $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 Delete { my $page = $nb->get_current_page(); $us->{wnd}{rwindow}->set_sensitive(0); gtkset_mousecursor_wait(); $error = 0; if ($page <= 0) { 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 = ugtk2->new(N("Warning : Deleting User"), grab => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); gtkadd($w->{window}, gtkpack_(Gtk2::VBox->new(0, 2), 0, Gtk2::Label->new(N("Deleting user %s\n Also perform the following actions\n", $username)), 0, $checkhome = Gtk2::CheckButton->new(N("Delete Home Directory :%s", $userEnt->HomeDir($GetValue))), 0, $checkspool = Gtk2::CheckButton->new(N("Delete Mailbox :/var/spool/mail/%s", $username)), 0, Gtk2::HSeparator->new, 0, gtkpack(Gtk2::HBox->new(1, 20), map { my $r = $_->[1]; gtksignal_connect(Gtk2::Button->new_from_stock($_->[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); my $member = $groupEnt->MemberName(1, 0); if ($groupEnt && (scalar(@$member) == 0)) { $groupEnt->Gid($GetValue) > 499 and $ctx->GroupDel($groupEnt) }; $checkhome->get_active() and $ctx->CleanHome($userEnt); $checkspool->get_active() and $ctx->CleanSpool($userEnt); Refresh($sysfilter, $stringsearch) } !$error and Gtk2->main_quit; $error = 0; }) } ([ 'gtk-cancel', 1 ], [ 'gtk-delete', 0 ])), ) ); $w->{rwindow}->show_all; $w->main; } elsif ($page == 1) { my $groupname = GetNameEntFromIter($grouptree, $gtree_model, 0); my $wg = ugtk2->new(N("Warning : Deleting Group"), grab => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); gtkadd($wg->{window}, gtkpack_(Gtk2::VBox->new(0, 2), 0, Gtk2::Label->new(N("Do you really want to delete the group %s\n", $groupname)), 0, Gtk2::HSeparator->new, 0, gtkpack(Gtk2::HBox->new(1, 20), map { my $r = $_->[1]; gtksignal_connect(Gtk2::Button->new_from_stock($_->[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)); $ctx->GroupDel($groupEnt); Refresh($sysfilter, $stringsearch) } } !$error and Gtk2->main_quit; $error = 0; }) } ([ 'gtk-cancel', 1 ], [ 'gtk-delete', 0 ])), ) ); $wg->{rwindow}->show_all; $wg->main; } $us->{wnd}{rwindow}->set_sensitive(1); gtkset_mousecursor_normal(); } # Gtk Facilities sub CreateTree { my ($tree_model) = @_; my $tree = Gtk2::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);} }); $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'; }); $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 == $Gtk2::Gdk::Keysyms{Return}); }); $tree } sub GtkEntryHidePass { my ($text) = @_; my $e = Gtk2::Entry->new(); $text and $e->set_text($text); $e->set_visibility(0); $e } sub GtkEntrySized { my ($i, $spac) = @_; my $e = Gtk2::Entry->new_with_max_length($i); $e->set_size_request($spac, 20); $e } sub BuildUui { gtkpack_(my $vbox = Gtk2::VBox->new(0, 2), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings => 5 }, [ N("Login") . " :", $us->{o}->{login} = Gtk2::Entry->new() ], [ N("Full Name") . " :", $us->{o}->{fullname} = Gtk2::Entry->new() ], [ N("Password") . " :", $us->{o}->{passwd} = GtkEntryHidePass() ], [ N("Confirm Password:"), $us->{o}->{confpasswd} = GtkEntryHidePass() ], [ N("Login Shell") . " :", $us->{o}->{shells} = Gtk2::OptionMenu->new ] ) ); $us->{o}->{shells}->set_popdown_strings(@{$ctx->GetUserShells()}); $us->{o}->{shells}->entry->set_text("/bin/bash"); $vbox } sub BuildGui { my $groupentry = @_; gtkpack_(my $vbox = Gtk2::VBox->new(0, 2), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings =>5 }, [ N("Group Name") . " :", $us->{o}->{groupname} = Gtk2::Entry->new() ] ) ); $vbox } sub Edit { my $w = ugtk2->new('userdrake', grab => 1, if_(!$::isEmbedded,transient => $us->{wnd}->{rwindow})); my $model = Gtk2::ListStore->new("Gtk2::Gdk::Pixbuf", "Glib::String", "Glib::Int"); my $tree = Gtk2::TreeView->new_with_model($model); my %g; my %u; my ($groupname, $username, $groupEnt, $userEnt, $members, $times, $min, $max, $warn, $inact, $primgid, $temp, $Gent); my @primgroup; my @pix = ($pixdir.'selected.png', $pixdir.'unselected.png'); $tree->get_selection->set_mode('browse'); $tree->append_column(my $check = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererPixbuf->new, 'pixbuf' => 0)); $check->{is_pixbuf_column} = 1; $tree->append_column(my $col = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 1)); $tree->set_headers_visible(0); my $p = $nb->get_current_page(); my $nbU = Gtk2::Notebook->new; my $nbG = Gtk2::Notebook->new; if ($p <= 0) { my $m = 0; my $s = 0; my ($vald, $mo, $ye) = (localtime)[3, 4, 5]; my $valy = $ye +1900; my $valm = $mo +1; gtkappend_page($nbU, gtkpack_(Gtk2::VBox->new(0, 2), 1, BuildUui(), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings => 5 }, [ N("Home") . " :", $us->{o}->{homedir} = Gtk2::Entry->new() ] ) ), gtkshow(Gtk2::Label->new(N("User Data")))); gtkappend_page($nbU, gtkpack_(Gtk2::VBox->new(0, 2), 0, $us->{o}->{acheckexpire} = Gtk2::CheckButton->new(N("Enable account expiration")), 0, gtkpack__(my $h = Gtk2::HBox->new(0, 10), Gtk2::Label->new(N("Account expires (YYYY-MM-DD):")), map { my $s = $_->[0]; my $value = $_->[1]; my $minv = $_->[2]; my $maxv = $_->[3]; my $spc = $_->[4]; $us->{o}->{$s} = Gtk2::SpinButton->new(Gtk2::Adjustment->new($value, $minv , $maxv, 1, 10, 10), 1, 0); } (['expy', $valy, 1970, 10000], ['expm', $valm, 1, 12], ['expd', $vald, 1, 31]), ), 0, Gtk2::HSeparator->new, 0, $us->{o}->{lockuser} = Gtk2::CheckButton->new(N("Lock User Account")) ), gtkshow(Gtk2::Label->new(N("Account Info")))); GrayBox($us->{o}->{acheckexpire}, $h, 0); gtkappend_page($nbU, gtkpack_(Gtk2::VBox->new(0, 2), 0, gtkpack_(Gtk2::HBox->new(0,5), 0, Gtk2::Label->new(N("User last changed password on : ")), 0, my $dayStr = Gtk2::Label->new(""), 0, my $month = Gtk2::Label->new(""), 0, my $dayInt = Gtk2::Label->new(""), 0, my $year = Gtk2::Label->new("") ), 0, Gtk2::HSeparator->new, 0, $us->{o}->{pcheckexpire} = Gtk2::CheckButton->new(N("Enable Password Expiration")), 1, gtkpack_(my $v = Gtk2::VBox->new(0,1), 1, create_packtable({ homogeneous => 1, col_spacings => 5, row_spacings => 5 }, [ N("Days before change allowed :"), $us->{o}->{dbca} = Gtk2::Entry->new_with_text(0) ], [ N("Days before change required :"), $us->{o}->{dbcr} = Gtk2::Entry->new_with_text(0) ], [ N("Days warning before change :"), $us->{o}->{bwbc} = Gtk2::Entry->new_with_text(0) ], [ N("Days before account inactive :"), $us->{o}->{dbai} = Gtk2::Entry->new_with_text(0) ] ) )), gtkshow(Gtk2::Label->new(N("Password Info")))); GrayBox($us->{o}->{pcheckexpire}, $v, 0); gtkappend_page($nbU, gtkpack_(Gtk2::VBox->new(0, 2), 0, Gtk2::Label->new(N("Select the groups that the user will be a member of:")), 1, create_scrolled_window($tree), 0, gtkpack_( Gtk2::HBox->new(0, 1), 0, Gtk2::Label->new(N("Primary Group")), 1, $us->{o}->{primgroup} = Gtk2::OptionMenu->new, ) ), gtkshow(Gtk2::Label->new(N("Groups")))); $username = GetNameEntFromIter($usertree, $utree_model,0); $us->{o}->{login}->set_text($username); $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); $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) { $times = TimeOfArray($lastchg, 0); $dayStr->set_text($times->{daystr}); $month->set_text($times->{month}); $dayInt->set_text($times->{dayint}); $year->set_text($times->{year}); } $min = $userEnt->ShadowMin($GetValue); $max =$userEnt->ShadowMax($GetValue); $warn = $userEnt->ShadowWarn($GetValue); $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); $members = $ctx->EnumerateGroupsByUser($username); $primgid = $userEnt->Gid($GetValue); $Gent = $ctx->LookupGroupById($primgid); foreach my $group (@sgroups) { if (any { $_ eq $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)); } elsif ($p == 1) { $nbG->set_size_request(300, 200); gtkappend_page($nbG, gtkpack_(Gtk2::VBox->new(0, 2), 1, BuildGui()), gtkshow(Gtk2::Label->new(N("Group Data")))); gtkappend_page($nbG, gtkpack_(Gtk2::VBox->new(0, 1), 0, Gtk2::Label->new(N("Select the users to join this group :")), 1, create_scrolled_window($tree)), gtkshow(Gtk2::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); $groupEnt = $ctx->LookupGroupByName($groupname); my $users = $ctx->UsersEnumerate(); my @susers = sort(@$users); $members = $ctx->EnumerateUsersByGroup($groupname); foreach my $user (@susers) { if (any { $_ eq $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]); } } } my ($ch, $name, $uEnt, $gEnt, $ugid, $Exp); gtkadd($w->{window}, gtkpack_(Gtk2::VBox->new(0,5), 1, !$p ? $nbU : $nbG, 0, Gtk2::HSeparator->new, 0, gtkpack(Gtk2::HBox->new(1, 20), map { my $retv = $_->[1]; gtksignal_connect(Gtk2::Button->new_from_stock($_->[0]), clicked => sub { if (!$retv) { if ($p <= 0) { $error = 0; %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($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 ($sec{SECURE_LEVEL} > 3 && length($u{pw}) < 6) { 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}); $username = $userEnt->UserName($GetValue); $model->foreach(sub { my ($mod, $path, $iter) = @_; $ch = $mod->get($iter, 2); $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) } !$error and Refresh($sysfilter, $stringsearch); } } } elsif ($p == 1) { $g{groupname} = $us->{o}->{groupname}->get_text(); $error = 0; if (!valid($g{groupname})) { RaiseError($us->{error}) }; if (!$error && $groupname ne $g{groupname}) {$groupEnt->GroupName($g{groupname})} $groupname = $groupEnt->GroupName($GetValue); $members = $ctx->EnumerateUsersByGroup($groupname); my $gid = $groupEnt->Gid($GetValue); !$error and $model->foreach(sub { my ($mod, $path, $iter) = @_; $ch = $mod->get($iter, 2); $name = $mod->get($iter, 1); if ($ch == 1) { if (!InArray($name, $members)) { $uEnt = $ctx->LookupUserByName($name); $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); } } } !$error and Gtk2->main_quit; $error = 0; gtkset_mousecursor_normal();}) } ([ 'gtk-cancel', 1 ], [ 'gtk-ok', 0 ])), )); $tree->show; $w->{rwindow}->show_all; $tree->signal_connect(button_press_event => sub { 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) } $us->{o}->{primgroup}->set_popdown_strings(@primgroup); $Gent and $us->{o}->{primgroup}->entry->set_text($Gent->GroupName($GetValue)); } } } } }); $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) } $us->{o}->{primgroup}->set_popdown_strings(@primgroup); $Gent and $us->{o}->{primgroup}->entry->set_text($Gent->GroupName($GetValue)); } } 0; }); $w->main } 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 $t; 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); $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, $no_button) = @_; my $dialog = gtkset_border_width(Gtk2::Dialog->new(), 10); $dialog->set_transient_for($us->{wnd}{rwindow}); $dialog->set_position('center-on-parent'); $dialog->set_title($title); $dialog->action_area->pack_start(gtkadd(Gtk2::HButtonBox->new, gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub { $dialog->destroy }) ), 0,0,0) unless $no_button; gtkset_modal($dialog, 1); } sub About { my $window_about = NewDialog(N("Userdrake")); my $tree_model = Gtk2::TreeStore->new("Glib::String", "Glib::String", "Glib::String"); my $list = Gtk2::TreeView->new_with_model($tree_model); $list->can_focus(0); each_index { $list->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => $::i)) } 0..2; $list->set_headers_visible(0); foreach my $row ([ N("Authors: "), '', '' ], [ '', 'Daouda Lo', '' ], ['', '', '']) { $tree_model->append_set(undef, [ map_index { $::i => $_ } @$row ]); } $list->get_selection()->set_mode('none'); gtkpack_($window_about->vbox, -r "$pixdir/userdrake.png" ? (0, Gtk2::Image->new_from_file("$pixdir/userdrake.png")) : (1, gtkmodify_font(Gtk2::Label->new(N("Users Management \n") . $us->{VERSION}), 'Bold 18'),), 1, $list, ); $window_about->show_all; } sub valid { $_[0] or $us->{error} = N("Name field is empty please provide a name"), return 0; $_[0] =~ /^[a-z]+?[a-z0-9_-]*?$/ or $us->{error} = N("The name must contain only lower cased latin letters, numbers, `-' and `_'"), return 0; length($_[0]) <= 32 or $us->{error} = N("Name is too long"), return 0; return 1; } sub RaiseError { my $w = ugtk2->new(N("Error"), grab => 1, center => 1, transient => $us->{wnd}->{rwindow}); $error = 1; gtkadd($w->{window}, gtkpack_(Gtk2::VBox->new, 1, Gtk2::Label->new($_[0]), 0, Gtk2::HSeparator->new, 0, gtksignal_connect(Gtk2::Button->new_from_stock('gtk-ok'), clicked => sub { Gtk2->main_quit }) ), ); $w->main } sub QuitGlobal { setVarsInSh($conffile, { FILTER => bool2text($sysfilter), }); gtkset_mousecursor_normal(); Gtk2->main_quit; }