ACC SHELL
#!/usr/bin/perl -w
use lib "/usr/lib/YaST2/agents_non_y2";
use ycp;
use strict;
my $max_system_uid = 499;
my $max_system_gid = 499;
my %last_uid = (
"local" => $max_system_uid + 1,
"system" => 0
);
my %users = ();
my %groups = ();
my %shadow_tmp = ();
my %shadow = ();
# used for check if values like uid,username are unique:
my %uids = ();
my %usernames = ();
my %homes = ();
my %gids = ();
my %groupnames = ();
# for each user there is a list of groups, where the user is contained
my %users_groups = ();
my %users_by_uidnumber = ();
my %groups_by_gidnumber = ();
my $plus_line_group = "";
my $plus_line_passwd = "";
my $plus_line_shadow = "";
my @plus_lines_group = ();
my @plus_lines_passwd = ();
my @plus_lines_shadow = ();
my @comments_group = ();
my @comments_passwd = ();
my @comments_shadow = ();
# error number
my $errno = 0;
#more information about the error
my $error_info = "";
# directory where user/group/shadow data should be found
my $base_directory = "/etc";
# compares 2 arrays; return 1 if they are equal
# (from perlfaq)
sub same_arrays {
my ($first, $second) = @_;
return 0 unless @$first == @$second;
for (my $i = 0; $i < @$first; $i++) {
return 0 if $first->[$i] ne $second->[$i];
}
return 1;
}
#---------------------------------------------
# read /etc/shadow and prepare global 'shadow_tmp' structure
sub read_shadow {
%shadow_tmp = ();
@plus_lines_shadow = ();
@comments_shadow = ();
my $o = open SHADOW, "< $base_directory/shadow";
if (!defined $o) {
y2warning ("$base_directory/shadow cannot be opened for reading!");
return 0;
}
foreach my $shadow_entry (<SHADOW>)
{
chomp $shadow_entry;
if ($shadow_entry eq "") {
y2warning ("empty line in shadow file...");
next;
}
my ($uname,$pass,$last_change,$min, $max, $warn, $inact, $expire, $flag)
= split(/:/,$shadow_entry);
my $first = substr ($uname, 0, 1);
if ($first eq "#") {
y2warning ("Found comment line in shadow file: '$shadow_entry'");
y2warning ("It will be moved to the end of file");
push @comments_shadow, $shadow_entry;
}
elsif ($first ne "+" && $first ne "-")
{
if (!defined $uname || $uname eq "") {
y2error ("strange line in shadow file: '$shadow_entry'");
$errno = 9;
return 9;
}
if (defined $shadow_tmp{$uname})
{
y2error ("duplicated username in /etc/shadow! Exiting...");
$errno = 3;
$error_info = $uname;
return 3;
}
$shadow_tmp{$uname} = {
"shadowlastchange" => $last_change,
"shadowwarning" => $warn,
"shadowinactive" => $inact,
"shadowexpire" => $expire,
"shadowmin" => $min,
"shadowmax" => $max,
"shadowflag" => $flag,
"userpassword" => $pass
};
}
else # plus line in /etc/shadow
{
$plus_line_shadow = $shadow_entry;
push @plus_lines_shadow, $shadow_entry;
}
}
close SHADOW;
return 0;
}
#---------------------------------------------
# read /etc/group and prepar global 'users_groups' structure
sub read_group {
%groups = ();
%gids = ();
%groupnames = ();
@plus_lines_group = ();
@comments_group = ();
my $o = open GROUP, "< $base_directory/group";
if (!defined $o) {
y2warning ("$base_directory/group cannot be opened for reading!");
return 0;
}
foreach my $group (<GROUP>) {
chomp $group;
if ($group eq "") {
y2warning ("empty line in group file ...");
next;
}
my ($groupname, $pass, $gid, $users) = split (/:/,$group);
my $first = substr ($groupname, 0, 1);
if ($first eq "#") {
y2warning ("Found comment line in group file: '$group'");
y2warning ("It will be moved to the end of file");
push @comments_group, $group;
}
elsif ( $first ne "+" && $first ne "-" ) {
if (!defined $pass || !defined $gid || !defined $users ||
$gid eq "") {
y2error ("strange line in group file: '$group'");
$errno = 8;
$error_info = "$group";
return 8;
}
my $group_type = "local";
if (($gid <= $max_system_gid || $groupname eq "nobody" ||
$groupname eq "nogroup") &&
($groupname ne "users"))
{
$group_type = "system";
}
# check for duplicates...
if (defined $gids{$group_type}{$gid})
{
y2warning ("duplicated gid ($gid) in /etc/group");
$gids{$group_type}{$gid} = $gids{$group_type}{$gid} + 1;
}
else
{
$gids{$group_type}{$gid} = 1;
}
if (defined $groupnames{"local"}{$groupname} ||
defined $groupnames{"system"}{$groupname})
{
y2error ("duplicated groupname in /etc/group! Exiting...");
$errno = 6;
$error_info = $groupname;
return 6;
}
else
{
$groupnames{$group_type}{$groupname} = 1;
}
# for each user generate list of groups, where the user is contained
my @userlist = split(/,/,$users);
my %userlist = ();
foreach my $u (@userlist) {
$userlist{$u} = 1;
$users_groups{$u}{$groupname} = 1;
}
$groups{$group_type}{$groupname} = {
"cn" => $groupname,
"gidnumber" => $gid,
"userlist" => \%userlist,
"type" => $group_type,
"userpassword" => $pass,
"more_users" => {}
};
if (!defined $groups_by_gidnumber{$group_type}{$gid}) {
$groups_by_gidnumber{$group_type}{$gid} = {};
}
$groups_by_gidnumber{$group_type}{$gid}{$groupname} = 1;
}
else # save the possible "+"/"-" entries
{
$plus_line_group = $group;
push @plus_lines_group, $group;
}
}
close GROUP;
return 0;
}
# --------------------------------------------------------------------
# read /etc/passwd
sub read_passwd {
my $o = open PASSWD, "< $base_directory/passwd";
if (!defined $o) {
y2warning ("$base_directory/passwd cannot be opened for reading!");
return 0;
}
%users = ();
%shadow = ();
%uids = ();
%usernames = ();
%homes = ();
@plus_lines_passwd = ();
@comments_passwd = ();
foreach my $user (<PASSWD>) {
chomp $user;
if ($user eq "") {
y2warning ("empty line in passwd file...");
next;
}
my ($username, $password, $uid, $gid, $full, $home, $shell)
= split(/:/,$user);
my $first = substr ($username, 0, 1);
if ($first eq "#") {
y2warning ("Found comment line in passwd file: '$user'");
y2warning ("It will be moved to the end of file");
push @comments_passwd, $user;
}
elsif ($first ne "+" && $first ne "-") {
if (!defined $password || !defined $uid || !defined $gid ||
!defined $full || !defined $home || !defined $shell ||
$username eq "" || $uid eq "" || $gid eq "") {
y2error ("strange line in passwd file: '$user'");
$errno = 7;
$error_info = "$user";
return 7;
}
my $user_type = "local";
my $group_type = "";
my $groupname = "";
my %grouplist = ();
if (defined $groups_by_gidnumber{"system"}{$gid})
{
$group_type = "system";
}
if (defined $groups_by_gidnumber{"local"}{$gid})
{
$group_type = "local";
}
if ($group_type ne "")
{
$groupname = (keys %{$groups_by_gidnumber{$group_type}{$gid}})[0];
# modify default group's more_users entry
$groups{$group_type}{$groupname}{"more_users"}{$username} = 1;
}
# add the grouplist
if (defined $users_groups{$username}) {
%grouplist = %{$users_groups{$username}};
}
if (($uid <= $max_system_uid) || ($username eq "nobody")) {
$user_type = "system";
if ($last_uid{"system"} < $uid && $username ne "nobody") {
$last_uid{"system"} = $uid;
}
}
else {
if ($last_uid{"local"} < $uid) {
$last_uid{"local"} = $uid;
}
}
my $encoding = "";
if ($encoding ne "") {
from_to ($full, $encoding, "utf-8");
}
my $colon = index ($full, ",");
my $additional = "";
if ( $colon > -1)
{
$additional = $full;
$full = substr ($additional, 0, $colon);
$additional = substr ($additional, $colon + 1,
length ($additional));
}
# check for duplicates in /etc/passwd:
if (defined $uids{$user_type}{$uid})
{
y2warning ("duplicated UID in /etc/passwd: $uid");
$uids{$user_type}{$uid} = $uids{$user_type}{$uid} + 1;
}
else
{
$uids{$user_type}{$uid} = 1;
}
if (defined $usernames{"local"}{$username} ||
defined $usernames{"system"}{$username})
{
y2error ("duplicated username in /etc/passwd! Exiting...");
$errno = 2;
$error_info = $username;
return 2;
}
else
{
$usernames{$user_type}{$username} = 1;
}
if ($home ne "")
{
$homes{$user_type}{$home} = 1;
}
my @grouplist = keys (%grouplist);
# such map we would like to export from the read script...
$users{$user_type}{$username} = {
"addit_data" => $additional,
"cn" => $full,
"homedirectory" => $home,
"uid" => $username,
"uidnumber" => $uid,
"gidnumber" => $gid,
"loginshell" => $shell,
"groupname" => $groupname,
"grouplist" => \%grouplist,
"userpassword" => undef,
"type" => $user_type
};
if (! defined $shadow_tmp{$username}) {
y2debug ("There is no shadow entry for user $username.");
}
else {
# divide shadow map accoring to user type
$shadow{$user_type}{$username} = $shadow_tmp{$username};
}
if (!defined $users_by_uidnumber{$user_type}{$uid}) {
$users_by_uidnumber{$user_type}{$uid} = {};
}
$users_by_uidnumber{$user_type}{$uid}{$username} = 1;
}
else # the "+" entry in passwd
{
$plus_line_passwd = $user;
push @plus_lines_passwd, $user;
}
}
close PASSWD;
return 0;
}
#---------------------------------------------
# write map of users to the file
sub write_passwd {
my %users_w = %{$_[0]};
# do not allow user to remove whole passwd
if (!%users_w) {
%users_w = %users;
}
my $o = open PASSWD, "> $base_directory/passwd";
if (!defined $o) {
y2error ("$base_directory/passwd cannot be opened for writing!");
$errno = 100;
return 0;
}
foreach my $type (sort {$b cmp $a} keys %users_w) {
if ($type ne "local" && $type ne "system") {
next;
}
foreach my $username (sort keys %{$users_w{$type}}) {
my %user = %{$users_w{$type}{$username}};
my $pass = "x";
my $cn = $user{"cn"} || "";
if (defined $user{"addit_data"} && $user{"addit_data"} ne "") {
$cn .= ",".$user{"addit_data"};
}
my $userline = join (":", (
$user{"uid"} || "",
$pass,
$user{"uidnumber"} || 0,
$user{"gidnumber"} || 0,
$cn,
$user{"homedirectory"} || "",
$user{"loginshell"} || "",
));
if (defined $userline) {
print PASSWD "$userline\n";
}
}
}
if (@comments_passwd > 0) {
foreach my $comment (@comments_passwd) {
print PASSWD "$comment\n";
}
}
if (@plus_lines_passwd > 0) {
foreach my $plusline (@plus_lines_passwd) {
print PASSWD "$plusline\n";
}
}
close PASSWD;
return 1;
}
#---------------------------------------------
# write map of shadow info to the file
sub write_shadow {
my %shadow_w = %{$_[0]};
# do not allow user to remove whole shadow
if (!%shadow_w) {
%shadow_w = %shadow;
}
my $o = open SHADOW, "> $base_directory/shadow";
if (!defined $o) {
y2error ("$base_directory/shadow cannot be opened for writing!");
$errno = 101;
return 0;
}
foreach my $type (sort {$b cmp $a} keys %shadow_w ) {
if ($type ne "local" && $type ne "system") {
next;
}
foreach my $uname (sort keys %{$shadow_w{$type}}) {
my %shadow_entry = %{$shadow_w{$type}{$uname}};
foreach my $key ("shadowwarning", "shadowinactive", "shadowexpire", "shadowflag", "userpassword") {
if (!defined $shadow_entry{$key}) {
$shadow_entry{$key} = "";
}
}
my $shadowline = join (":", (
$uname,
$shadow_entry{"userpassword"},
$shadow_entry{"shadowlastchange"},
$shadow_entry{"shadowmin"},
$shadow_entry{"shadowmax"},
$shadow_entry{"shadowwarning"},
$shadow_entry{"shadowinactive"},
$shadow_entry{"shadowexpire"},
$shadow_entry{"shadowflag"}
));
if (defined $shadowline) {
print SHADOW "$shadowline\n";
}
}
}
if (@comments_shadow > 0) {
foreach my $comment (@comments_shadow) {
print SHADOW "$comment\n";
}
}
if (@plus_lines_shadow > 0) {
foreach my $plusline (@plus_lines_shadow) {
print SHADOW "$plusline\n";
}
}
close SHADOW;
}
#---------------------------------------------
# write map of groups to the file
sub write_group {
my %groups_w = %{$_[0]};
# do not allow user to remove whole group
if (!%groups_w) {
%groups_w = %groups;
}
my $o = open GROUP, "> $base_directory/group";
if (!defined $o) {
y2error ("$base_directory/group cannot be opened for writing!");
$errno = 102;
return 0;
}
# sort order: system items go before local ones
foreach my $type (sort {$b cmp $a} keys %groups_w ) {
if ($type ne "local" && $type ne "system") {
next;
}
# sort order: id
foreach my $groupname (sort keys %{$groups_w{$type}}) {
my %group = %{$groups_w{$type}{$groupname}};
my $pass = "x";
if (defined $group{"userpassword"}) {
$pass = $group{"userpassword"};
}
my @group_entry = (
$group{"cn"},
$pass,
$group{"gidnumber"} || 0,
join (",", sort keys %{$group{"userlist"}})
);
my $groupline = join (":", @group_entry);
if (defined $groupline) {
print GROUP "$groupline\n";
}
}
}
if (@comments_group > 0) {
foreach my $comment (@comments_group) {
print GROUP "$comment\n";
}
}
if (@plus_lines_group > 0) {
foreach my $plusline (@plus_lines_group) {
print GROUP "$plusline\n";
}
}
close GROUP;
}
sub read_all () {
my $error = 0;
$error = read_shadow ();
if ($error == 0) {
$error = read_group ();
}
if ($error == 0) {
$error = read_passwd ();
}
return $error;
}
# --------------------------------------- main -----------------------------
while ( <STDIN> )
{
my ($command, $path, $argument) = ycp::ParseCommand ($_);
y2debug ("command: $command, path: $path");
if ( $command eq "Execute" ) {
if ( $path eq '.init' ) {
if (ref ($argument) eq "HASH") {
if (defined ($argument->{"max_system_uid"})) {
$max_system_uid = $argument->{"max_system_uid"};
}
if (defined ($argument->{"max_system_gid"})) {
$max_system_gid = $argument->{"max_system_gid"};
}
if (defined ($argument->{"base_directory"})) {
$base_directory = $argument->{"base_directory"};
}
}
my $error = read_all ();
if ($error > 0) {
ycp::Return ("false");
}
else {
ycp::Return("true");
}
}
else {
y2error ("wrong path ($path) or argument type:", ref ($argument));
ycp::Return("false");
}
}
elsif ( $command eq "Read" ) {
# check if initialization was done
if (!%users && $path ne '.error' && $path ne '.error.info') {
my $error = read_all ();
if ($error > 0) {
ycp::Return ({});
}
}
if ($path eq '.error') {
ycp::Return ($errno);
}
elsif ($path eq '.error.info') {
ycp::Return ($error_info);
}
elsif ( $path eq '.local.users' ) {
ycp::Return (\%{$users{"local"}}, 1);
}
elsif ( $path eq '.system.users' ) {
ycp::Return (\%{$users{"system"}}, 1);
}
elsif ( $path eq '.local.shadow' ) {
ycp::Return (\%{$shadow{"local"}}, 1);
}
elsif ( $path eq '.system.shadow' ) {
ycp::Return (\%{$shadow{"system"}}, 1);
}
elsif ( $path eq '.local.groups' ) {
ycp::Return (\%{$groups{"local"}}, 1);
}
elsif ( $path eq '.system.groups' ) {
ycp::Return (\%{$groups{"system"}}, 1);
}
elsif ( $path eq '.local.users.by_name' ) {
y2warning ("$command $path is deprecated");
ycp::Return ({});
}
elsif ( $path eq '.system.users.by_name' ) {
y2warning ("$command $path is deprecated");
ycp::Return ({});
}
elsif ( $path eq '.local.users.by_uidnumber' ) {
ycp::Return (\%{$users_by_uidnumber{"local"}}, 1);
}
elsif ( $path eq '.system.users.by_uidnumber' ) {
ycp::Return (\%{$users_by_uidnumber{"system"}}, 1);
}
elsif ( $path eq '.local.users.uids' ) {
ycp::Return (\%{$uids{"local"}}, 1);
}
elsif ( $path eq '.system.users.uids' ) {
ycp::Return (\%{$uids{"system"}}, 1);
}
elsif ( $path eq '.local.users.usernames' ) {
ycp::Return (\%{$usernames{"local"}}, 1);
}
elsif ( $path eq '.system.users.usernames' ) {
ycp::Return (\%{$usernames{"system"}}, 1);
}
elsif ( $path eq '.local.users.homes' ) {
ycp::Return (\%{$homes{"local"}}, 1);
}
elsif ( $path eq '.system.users.homes' ) {
ycp::Return (\%{$homes{"system"}}, 1);
}
elsif ( $path eq '.local.users.last_uid' ) {
ycp::Return ($last_uid{"local"}, 1);
}
elsif ( $path eq '.system.users.last_uid' ) {
ycp::Return ($last_uid{"system"}, 1);
}
elsif ( $path eq '.local.groups.by_name' ) {
y2warning ("$command $path is deprecated");
ycp::Return ({});
}
elsif ( $path eq '.system.groups.by_name' ) {
y2warning ("$command $path is deprecated");
ycp::Return ({});
}
elsif ( $path eq '.local.groups.by_gidnumber' ) {
ycp::Return (\%{$groups_by_gidnumber{"local"}}, 1);
}
elsif ( $path eq '.system.groups.by_gidnumber' ) {
ycp::Return (\%{$groups_by_gidnumber{"system"}}, 1);
}
elsif ( $path eq '.local.groups.gids' ) {
ycp::Return (\%{$gids{"local"}}, 1);
}
elsif ( $path eq '.system.groups.gids' ) {
ycp::Return (\%{$gids{"system"}}, 1);
}
elsif ( $path eq '.local.groups.groupnames' ) {
ycp::Return (\%{$groupnames{"local"}}, 1);
}
elsif ( $path eq '.system.groups.groupnames' ) {
ycp::Return (\%{$groupnames{"system"}}, 1);
}
elsif ( $path eq '.passwd.plusline' ) {
y2warning ("$command $path is deprecated, use '.passwd.pluslines' instead!");
ycp::Return ($plus_line_passwd);
}
elsif ( $path eq '.shadow.plusline' ) {
y2warning ("$command $path is deprecated, use '.shadow.pluslines' instead!");
ycp::Return ($plus_line_shadow);
}
elsif ( $path eq '.group.plusline' ) {
y2warning ("$command $path is deprecated, use '.group.pluslines' instead!");
ycp::Return ($plus_line_group);
}
elsif ( $path eq '.passwd.pluslines' ) {
ycp::Return (\@plus_lines_passwd, 1);
}
elsif ( $path eq '.shadow.pluslines' ) {
ycp::Return (\@plus_lines_shadow, 1);
}
elsif ( $path eq '.group.pluslines' ) {
ycp::Return (\@plus_lines_group, 1);
}
elsif ( $path eq '.passwd.comments' ) {
ycp::Return (\@comments_passwd, 1);
}
elsif ( $path eq '.group.comments' ) {
ycp::Return (\@comments_group, 1);
}
elsif ( $path eq '.shadow.comments' ) {
ycp::Return (\@comments_shadow, 1);
}
else {
y2error ("wrong path ($path) or argument: ", ref ($argument));
ycp::Return("false");
}
}
elsif ( $command eq "Write" )
{
if ( $path eq '.users' && ref ($argument) eq "HASH" ) {
if (write_passwd ($argument)) {
ycp::Return ("true");
}
else {
ycp::Return ("false");
}
}
elsif ( $path eq '.shadow' && ref ($argument) eq "HASH" ) {
if (write_shadow ($argument)) {
ycp::Return ("true");
}
else {
ycp::Return ("false");
}
}
elsif ( $path eq '.groups' && ref ($argument) eq "HASH" ) {
if (write_group ($argument)) {
ycp::Return ("true");
}
else {
ycp::Return ("false");
}
}
elsif ( $path eq '.passwd.plusline' ) {
y2warning ("$command $path deprecated; use '.passwd.pluslines' instead!");
if ($argument eq $plus_line_passwd) {
ycp::Return ("false");
}
else {
@plus_lines_passwd = ($argument);
ycp::Return ("true");
}
}
elsif ( $path eq '.group.plusline' ) {
y2warning ("$command $path deprecated; use '.group.pluslines' instead!");
if ($argument eq $plus_line_group) {
ycp::Return ("false");
}
else {
@plus_lines_group = ($argument);
ycp::Return ("true");
}
}
elsif ( $path eq '.shadow.plusline' ) {
y2warning ("$command $path deprecated; use '.shadow.pluslines' instead!");
if ($argument eq $plus_line_shadow) {
ycp::Return ("false");
}
else {
@plus_lines_shadow = ($argument);
ycp::Return ("true");
}
}
elsif ($path eq '.passwd.pluslines' && ref ($argument) eq "ARRAY") {
if (same_arrays (\@plus_lines_passwd, $argument)) {
ycp::Return ("false");
}
else {
@plus_lines_passwd = @$argument;
y2milestone ("new plus lines in passwd: ", @plus_lines_passwd);
ycp::Return ("true");
}
}
elsif ($path eq '.shadow.pluslines' && ref ($argument) eq "ARRAY") {
if (same_arrays (\@plus_lines_shadow, $argument)) {
ycp::Return ("false");
}
else {
@plus_lines_shadow = @$argument;
y2milestone ("new plus lines in shadow: ", @plus_lines_shadow);
ycp::Return ("true");
}
}
elsif ($path eq '.group.pluslines' && ref ($argument) eq "ARRAY") {
if (same_arrays (\@plus_lines_group, $argument)) {
ycp::Return ("false");
}
else {
@plus_lines_group = @$argument;
y2milestone ("new plus lines in group: ", @plus_lines_group);
ycp::Return ("true");
}
}
else {
y2error ("wrong path ($path) or argument type: ", ref ($argument));
ycp::Return("false");
}
}
elsif ( $command eq "result")
{
exit;
}
else
{
y2error ("wrong command: $command");
ycp::Return("wrong command ($command)");
}
}
# end
ACC SHELL 2018