ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/UsersSimple.pm

#! /usr/bin/perl -w
#
# File:		modules/UsersSimple.pm
# Package:	Configuration of users and groups
# Summary:	module for first stage user configuration
#
# $Id: UsersSimple.pm 61042 2010-02-26 13:48:03Z jsuchome $
#

package UsersSimple;

use strict;
use Data::Dumper;

use YaST::YCP qw(:LOGGING sformat);
use YaPI;

textdomain("users");

our %TYPEINFO;


# What client to call after authentication dialog during installation:
# could be "users","nis" or "ldap", for more see inst_auth.ycp
my $after_auth			= "users";

# If kerberos configuration should be called after authentication
# during installation (F120214)
my $run_krb_config		= 0;

my $root_password		= "";

# root password already written
my $root_password_written	= 0;

# users from 1st stage already written
my $users_written		= 0;

# only for first stage, remember if root pw dialog should be skipped
my $skip_root_dialog		= 0;

# data of user configured during installation
#my %user			= ();

# data of users configured during installation
my @users			= ();


# password encryption method
my $encryption_method		= "blowfish";

# mail alias for root
my $root_alias			= "";

my %min_pass_length	= (
    "local"		=> 5,
    "system"		=> 5,
    "ldap"		=> 5
);

my %max_pass_length	= (
    "local"		=> 72,
    "system"		=> 72,
    "ldap"		=> 72
);


# Number of sigificant characters in the password for given encryption method
my %max_lengths			= (
    "des"	=> 8,
    "md5"	=> 127,
    "blowfish"	=> 72,
);

# name of user that should be logged in automatically
my $autologin_user		= "";

# path to cracklib dictionary
my $cracklib_dictpath		= "";

# if cracklib is used for password checking
my $use_cracklib 		= 1;

# this is yast2-users internal check now, feature was removed from PAM
my $obscure_checks 		= 1;

# User/group names must match the following regex expression. (/etc/login.defs)
my $character_class 		= "[[:alpha:]_][[:alnum:]_.-]*[[:alnum:]_.\$-]\\?";

my $max_length_login 	= 32; # reason: see for example man utmp, UT_NAMESIZE
my $min_length_login 	= 2;

# see SYSTEM_UID_MAX and SYSTEM_GID_MAX in /etc/login.defs
my $max_system_uid	= 499;

# maps for user data read in 1st stage ('from previous installation')
my %imported_users		= ();
my %imported_shadow		= ();

# if importing users during installation is possible
my $import_available;

# if check for LDAP/Kerberos via DNS was done
my $network_methods_checked	= 0;

# prevent re-reading data map with 1st stage settingss
my $first_stage_data_not_read	= 1;

# note if users configuration should be run or skipped in 2nd stage (bnc#450231)
my $skip_user_creation		= 0;

##------------------------------------
##------------------- global imports

YaST::YCP::Import ("Directory");
YaST::YCP::Import ("FileUtils");
YaST::YCP::Import ("Hostname");
YaST::YCP::Import ("InstExtensionImage");
YaST::YCP::Import ("Language");
YaST::YCP::Import ("Mode");
YaST::YCP::Import ("NetworkService");
YaST::YCP::Import ("ProductControl");
YaST::YCP::Import ("SCR");
YaST::YCP::Import ("Stage");
YaST::YCP::Import ("SystemFilesCopy");
YaST::YCP::Import ("UsersUI");

# known system users (hard-written here to check user name conflicts)
# number may mean the UID (but it don't have to be defined)
my %system_users	= (
	"root"		=> 0,
	"bin"		=> 1,
	"uucp"		=> 10,
	"daemon"	=> 2,
	"lp"		=> 4,
	"mail"		=> 8,
	"news" 		=> 9,
	"uucp" 		=> 10,
	"games" 	=> 12,
	"man" 		=> 13,
	"at" 		=> 25,
	"wwwrun"	=> 30,
	"ftp" 		=> 40,
	"named" 	=> 0,
	"gdm" 		=> 0,
	"postfix" 	=> 51,
	"sshd" 		=> 71,
	"ntp" 		=> 74,
	"ldap" 		=> 76,
	"nobody" 	=> 65534,
	"amanda" 	=> 0,
	"vscan" 	=> 0,
	"bigsister" 	=> 0,
	"wnn" 		=> 0,
	"cyrus" 	=> 0,
	"dpbox" 	=> 0,
	"gnats" 	=> 0,
	"gnump3d" 	=> 0,
	"hacluster" 	=> 0,
	"irc" 		=> 0,
	"mailman" 	=> 0,
	"mdom" 		=> 0,
	"mysql" 	=> 0,
	"oracle" 	=> 0,
	"postgres" 	=> 0,
	"pop" 		=> 0,
	"sapdb" 	=> 0,
	"snort" 	=> 0,
	"squid" 	=> 31,
	"stunnel" 	=> 0,
	"zope" 		=> 0,
	"radiusd" 	=> 0,
	"otrs" 		=> 0,
	"privoxy" 	=> 0,
	"vdr" 		=> 0,
	"icecream" 	=> 0,
	"bitlbee" 	=> 0,
	"dhcpd" 	=> 0,
	"distcc" 	=> 0,
	"dovecot" 	=> 0,
	"fax" 		=> 0,
	"partimag" 	=> 0,
	"avahi"		=> 0,
	"beagleindex"	=> 0,
	"casaauth"	=> 0,
	"dvbdaemon"	=> 0,
	"festival"	=> 0,
	"haldaemon"	=> 0,
	"icecast"	=> 0,
	"lighttpd"	=> 0,
	"nagios"	=> 0,
	"pdns"		=> 0,
	"polkituser"	=> 0,
	"pound"		=> 0,
	"pulse"		=> 0,
	"quagga"	=> 0,
	"sabayon-admin"	=> 0,
	"tomcat"	=> 0,
	"pegasus"	=> 0,
	"cimsrvr"	=> 0,
	"ulogd"		=> 0,
	"uuidd"		=> 0,
	"suse-ncc"	=> 0,
	"messagebus"    => 0,
	"nx"      	=> 0
);

# check the boolean value, return 0 or 1
sub bool {

    my $param = $_[0];
    if (!defined $param) {
	return 0;
    }
    if (ref ($param) eq "YaST::YCP::Boolean") {
	return $param->value();
    }
    return $param;
}

##------------------------------------
# set new cracklib dictionary path
BEGIN { $TYPEINFO{SetCrackLibDictPath} = ["function", "void", "string"];}
sub SetCrackLibDictPath {
    my $self	= shift;
    $cracklib_dictpath	= shift;
}

##------------------------------------
# return the value of current encryption method
BEGIN { $TYPEINFO{EncryptionMethod} = ["function", "string"];}
sub EncryptionMethod {
    return $encryption_method;
}

# is cracklib used for password checking?
BEGIN { $TYPEINFO{CrackLibUsed} = ["function", "boolean"]; }
sub CrackLibUsed {
    return $use_cracklib;
}

# set the new value of cracklib usage for password checking
BEGIN { $TYPEINFO{UseCrackLib} = ["function", "void", "boolean"]; }
sub UseCrackLib {
    my $self	= shift;
    my $crack	= shift;
    $use_cracklib = bool ($crack) if (defined $crack);
}

# are 'obscure checks' used for password checking?
BEGIN { $TYPEINFO{ObscureChecksUsed} = ["function", "boolean"]; }
sub ObscureChecksUsed {
    return $obscure_checks;
}

# set the new value of 'obscure checks' usage for password checking
BEGIN { $TYPEINFO{UseObscureChecks} = ["function", "void", "boolean"]; }
sub UseObscureChecks {
    my $self	= shift;
    my $checks	= shift;
    $obscure_checks = bool ($checks) if (defined $checks);
}


##------------------------------------
# set new encryption method
BEGIN { $TYPEINFO{SetEncryptionMethod} = ["function", "void", "string"];}
sub SetEncryptionMethod {

    my $self	= shift;
    my $method	= shift;
    if ($encryption_method ne $method) {
	$encryption_method 		= $method;
	if (defined $max_lengths{$encryption_method}) {
	    $max_pass_length{"local"}	= $max_lengths{$encryption_method};
	    $max_pass_length{"system"}	= $max_lengths{$encryption_method};
	}
    }
}

BEGIN { $TYPEINFO{GetAutologinUser} = ["function", "string"]; }
sub GetAutologinUser {
    return $autologin_user;
}

BEGIN { $TYPEINFO{AutologinUsed} = ["function", "boolean"]; }
sub AutologinUsed {
    return bool ($autologin_user ne "");
}

BEGIN { $TYPEINFO{SetAutologinUser} = ["function", "void", "string"]; }
sub SetAutologinUser {
    my $self		= shift;
    $autologin_user	= shift;
}

BEGIN { $TYPEINFO{GetRootAlias} = ["function", "string"]; }
sub GetRootAlias {
    return $root_alias;
}

BEGIN { $TYPEINFO{SetRootAlias} = ["function", "void", "string"]; }
sub SetRootAlias {
    my $self		= shift;
    $root_alias		= shift;
}

BEGIN { $TYPEINFO{AfterAuth} = ["function", "string"];}
sub AfterAuth {
    return $after_auth;
}

BEGIN { $TYPEINFO{SetAfterAuth} = ["function", "void", "string"];}
sub SetAfterAuth {
    my $self	= shift;
    $after_auth = $_[0];
}

# return the value of run_krb_config (should the kerberos config be run?)
BEGIN { $TYPEINFO{KerberosConfiguration} = ["function", "boolean"];}
sub KerberosConfiguration {
    return bool ($run_krb_config);
}

# set the new value for run_krb_config
BEGIN { $TYPEINFO{SetKerberosConfiguration} = ["function", "void", "boolean"];}
sub SetKerberosConfiguration {
    my ($self, $krb)	= @_;
    $run_krb_config = bool ($krb) if (defined $krb);
}

# note if users configuration should be run in 2nd stage
BEGIN { $TYPEINFO{SkipUserCreation} = ["function", "void", "boolean"];}
sub SkipUserCreation {
    my ($self, $skip)	= @_;
    $skip_user_creation = bool ($skip);
}

# return the value of skip_user_creation
BEGIN { $TYPEINFO{UserCreationSkipped} = ["function", "boolean"];}
sub UserCreationSkipped {
    return bool ($skip_user_creation);
} 

##------------------------------------
# Returns the map of user configured during installation
# @return the map of user
BEGIN { $TYPEINFO{GetUser} = [ "function",
    ["map", "string", "any" ]];
}
sub GetUser {

    my %ret	= ();
    %ret	= %{$users[0]} if (defined $users[0]);
    return \%ret;
}

##------------------------------------
# Returns the list users configured during installation
# @return the list of user maps
BEGIN { $TYPEINFO{GetUsers} = [ "function", ["list", "any" ]]; }
sub GetUsers {

    return \@users;
}

##------------------------------------
# Saves the user data into the map
# @param data user initial data (could be an empty map)
BEGIN { $TYPEINFO{SetUser} = ["function",
    "string",
    ["map", "string", "any" ]];		# data to fill in
}
sub SetUser {

    my $self	= shift;
    my $data	= shift;
    if (defined $data && (ref ($data) eq "HASH")) {
	my %user	= %{$data};
	@users	= ();
	push @users, %user;
    }
    return "";
}

##------------------------------------
# Saves the user data into the list
# @param list with user data maps (could be empty)
BEGIN { $TYPEINFO{SetUsers} = ["function",
    "string",
    ["list", "any" ]];		# data to fill in
}
sub SetUsers {

    my $self	= shift;
    my $data	= shift;
    if (defined $data && (ref ($data) eq "ARRAY")) {
	@users	= @{$data};
    }
    return "";
}

# were users from 1st stage already written?
BEGIN { $TYPEINFO{UsersWritten} = ["function", "boolean"];}
sub UsersWritten {
    return bool ($users_written);
}

# was root password written in 1st stage?
BEGIN { $TYPEINFO{RootPasswordWritten} = ["function", "boolean"];}
sub RootPasswordWritten {
    return bool ($root_password_written);
}

##------------------------------------
# save the root password into variable
BEGIN { $TYPEINFO{SetRootPassword} = ["function", "void", "string"];}
sub SetRootPassword {

    my $self		= shift;
    $root_password 	= $_[0];
}

##------------------------------------
BEGIN { $TYPEINFO{GetRootPassword} = ["function", "string"];}
sub GetRootPassword {
    return $root_password;
}

# remember if the checkbox 'Use this password for root' was checked
BEGIN { $TYPEINFO{SkipRootPasswordDialog} = ["function", "void", "boolean"];}
sub SkipRootPasswordDialog {
    my $self	= shift;
    my $skip	= shift;
    $skip_root_dialog = bool ($skip) if (defined $skip);
}

# was the checkbox 'Use this password for root' was checked
BEGIN { $TYPEINFO{RootPasswordDialogSkipped} = ["function", "boolean"];}
sub RootPasswordDialogSkipped {
    return bool ($skip_root_dialog);
}


##------------------------------------
# crypt given password
BEGIN { $TYPEINFO{CryptPassword} = ["function",
    "string", "string"];
}
sub CryptPassword {

    my $self	= shift;
    my $pw	= shift;
    
    return $pw if (!defined $pw);
    return UsersUI->HashPassword (lc ($encryption_method), $pw);
}

##------------------------------------
# Writes password of superuser
# This is called during install
# @return true on success
BEGIN { $TYPEINFO{WriteRootPassword} = ["function", "boolean"];}
sub WriteRootPassword {

    my $self		= shift;
    my $crypted		= $self->CryptPassword ($root_password, "system");
    return SCR->Write (".target.passwd.root", $crypted);
}

# "-" means range! -> at the begining or at the end!
# now CHARACTER_CLASS from /etc/login.defs is used
my $valid_logname_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-";

my $valid_password_chars = "[-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#\$%^&*() ,;:._+/|?{}=\['\"`~<>]|]";# the ']' is or-ed...

# error popup	
my $valid_password_message = __("The password may only contain the following characters:
0-9, a-z, A-Z, and any of \"`~!\@#\$%^&* ,.;:._-+/|\?='{[(<>)]}\\\".
Try again.");

my $valid_home_chars = "[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@/_.-]";

##------------------------------------
BEGIN { $TYPEINFO{ValidLognameChars} = ["function", "string"]; }
sub ValidLognameChars {
    return $valid_logname_chars;
}

##------------------------------------
BEGIN { $TYPEINFO{ValidPasswordChars} = ["function", "string"]; }
sub ValidPasswordChars {
    return $valid_password_chars;
}

##------------------------------------
BEGIN { $TYPEINFO{ValidHomeChars} = ["function", "string"]; }
sub ValidHomeChars {
    return $valid_home_chars;
}

##------------------------------------
BEGIN { $TYPEINFO{ValidPasswordMessage} = ["function", "string"]; }
sub ValidPasswordMessage {
    return $valid_password_message;
}

##------------------------------------
# Return the part of help text about valid password characters
BEGIN { $TYPEINFO{ValidPasswordHelptext} = ["function", "string"]; }
sub ValidPasswordHelptext {
    # help text (default part shown in more places)
    return __("<p>
For the password, use only characters that can be found on an English keyboard
layout.  In cases of system error, it may be necessary to log in without a
localized keyboard layout.
</p>");
}

##------------------------------------
BEGIN { $TYPEINFO{GetMinPasswordLength} = ["function", "integer", "string"]; }
sub GetMinPasswordLength {

    my $self		= shift;
    my $type		= shift;
    if (defined $type && defined ($min_pass_length{$type})) {
	return $min_pass_length{$type};
    }
    else { return 5;}
}

##------------------------------------
# Set the minimum password length for given user type
# @param type
# @param length
BEGIN { $TYPEINFO{SetMinPasswordLength} = ["function",
    "void", "string", "integer"];
}
sub SetMinPasswordLength {
    my ($self, $type, $len)	= @_;
    $min_pass_length{$type}	= $len;
}

##------------------------------------
BEGIN { $TYPEINFO{GetMaxPasswordLength} = ["function", "integer", "string"]; }
sub GetMaxPasswordLength {
    my $self		= shift;
    if (defined ($max_pass_length{$_[0]})) {
	return $max_pass_length{$_[0]};
    }
    else { return 8; }
}

##------------------------------------
# Set the maximum password length for given user type
# @param type
# @param length
BEGIN { $TYPEINFO{SetMaxPasswordLength} = ["function",
    "void", "string", "integer"];
}
sub SetMaxPasswordLength {
    my ($self, $type, $len)	= @_;
    $max_pass_length{$type}	= $len;
}

##------------------------------------
BEGIN { $TYPEINFO{GetMinLoginLength} = ["function", "integer" ]; }
sub GetMinLoginLength {
    my $self	= shift;
    return $min_length_login;
}

##------------------------------------
BEGIN { $TYPEINFO{GetMaxLoginLength} = ["function", "integer" ]; }
sub GetMaxLoginLength {
    my $self	= shift;
    return $max_length_login;
}


##---------------------------------------------------------------------------
## check functions

##------------------------------------
# check fullname contents
BEGIN { $TYPEINFO{CheckFullname} = ["function", "string", "string"]; }
sub CheckFullname {

    my ($self, $fullname)        = @_;

    if (defined $fullname && $fullname =~ m/[:,]/) {
	# error popup
        return __("The user's full name cannot contain
\":\" or \",\" characters.
Try again.");
    }
    return "";
}

##------------------------------------
# Just some simple checks for password contens
# @param username user or group name
# @param pw password
# @param user/group
# @return error message (password too simple) or empty string (OK)
BEGIN { $TYPEINFO{CheckObscurity} = ["function", "string",
    "string", "string", "string"];}
sub CheckObscurity {

    my $self		= shift;
    my $name		= shift;
    my $pw 		= shift;
    my $what		= shift;

    if ($pw =~ m/$name/) {
	if ($what eq "groups") {
	    # popup question
	    return __("You have used the group name as a part of the password.");
	}
	# popup question
        return __("You have used the username as a part of the password.");
    }

    # check for lowercase
    my $filtered 	= $pw;
    $filtered 		=~ s/[[:lower:]]//g;
    if ($filtered eq "") {
	# popup question
        return __("You have used only lowercase letters for the password.");
    }

    # check for uppercase
    $filtered 		= $pw;
    $filtered 		=~ s/[[:upper:]]//g;
    if ($filtered eq "") {
	# popup question
        return __("You have used only uppercase letters for the password.");
    }
    
    # check for palindroms
    $filtered 		= reverse $pw;
    if ($filtered eq $pw) {
	# popup question
        return __("You have used a palindrom for the password.");
    }

    # check for numbers
    $filtered 		= $pw;
    $filtered 		=~ s/[0-9]//g;
    if ($filtered eq "") {
	# popup question
        return __("You have used only digits for the password.");
    }
    return "";
}

##------------------------------------
# Checks if password is not too long
# @param pw password
# @param user/group type
BEGIN { $TYPEINFO{CheckPasswordMaxLength} = ["function",
    "string", "string", "string"];
}
sub CheckPasswordMaxLength {

    my $self		= shift;
    my $pw 		= shift;
    my $type		= shift;
    my $max_length 	= $self->GetMaxPasswordLength ($type);
    my $ret		= "";

    if (length ($pw) > $max_length) {
	# popup question
        $ret = sprintf (__("The password is too long for the current encryption method.
It will be truncated to %s characters."), $max_length);
    }
    return $ret;
}

##------------------------------------
# Try to crack password using cracklib
# @param pw password
# @return utility output: either "" or error message
BEGIN { $TYPEINFO{CrackPassword} = ["function", "string", "string"];}
sub CrackPassword {

    my $self	= shift;
    my $pw 	= shift;
    my $ret 	= "";

    if (!defined $pw || $pw eq "") {
	return $ret;
    }
    if (!defined $cracklib_dictpath || $cracklib_dictpath eq "" ||
	!FileUtils->Exists ("$cracklib_dictpath.pwd")) {
	$ret = SCR->Execute (".crack", $pw);
    }
    else {
	$ret = SCR->Execute (".crack", $pw, $cracklib_dictpath);
    }
    if (!defined ($ret)) { $ret = ""; }
    return $ret if ($ret eq "");
    return UsersUI->RecodeUTF ($ret);
}

##------------------------------------
# check the password of given user
# @param password
# @param user type
# return value is error message
BEGIN { $TYPEINFO{CheckPassword} = ["function", "string", "string", "string"]; }
sub CheckPassword {

    my ($self, $pw, $type)	= @_;
    my $min_length 	= $self->GetMinPasswordLength ($type);

    if ((!defined $pw) || ($pw eq "" && $min_length > 0)) {
	# error popup
	return __("No password entered.
Try again.");
    }

    my $filtered = $pw;
    $filtered =~ s/$valid_password_chars//g;
    $filtered =~ s/\\//g; # bug 175706

    if ($filtered ne "") {
	return $self->ValidPasswordMessage ();
    }
    return "";
}

# Check the password of given user or group: part 2, checking for
# problems that may be skipped (accepted) by user
# @param data map containing user/group name, password and type
#
# Merges all error reports and returns them in the list
BEGIN { $TYPEINFO{CheckPasswordUI} = ["function",
    ["list", "string"],
    ["map", "string", "any"]];
}
sub CheckPasswordUI {

    my ($self, $data)	= @_;
    my $pw		= $data->{"userPassword"} || "";
    my $name		= $data->{"uid"};
    $name		= ($data->{"cn"} || "") if (!defined $name);
    my $type		= $data->{"type"} || "local";
    my $min_length 	= $self->GetMinPasswordLength ($type);

    my @ret		= ();

    if ($pw eq "") {
	return \@ret;
    }

    if ($self->CrackLibUsed ()) {
	my $error = $self->CrackPassword ($pw);
	if ($error ne "") {
	    # error message
	    push @ret, sprintf (__("The password is too simple:
%s."), $error);
	}
    }
    
    if (1) {#$self->ObscureChecksUsed ()) {
	my $what	= "users";
	$what		= "groups" if (! defined $data->{"uid"});
	my $error	= $self->CheckObscurity ($name, $pw, $what);
	push @ret, $error if $error;
    }

    if (length ($pw) < $min_length) {
	# popup error, %i is number
	push @ret, sprintf (__("The password should have at least %i characters."), $min_length);
    }
    
    my $error = $self->CheckPasswordMaxLength ($pw, $type);
    push @ret, $error if $error;

    return \@ret;
}

##------------------------------------
# Check the length of given user name
# @param user name
# @return error message
BEGIN { $TYPEINFO{CheckUsernameLength} = ["function", "string", "string"]; }
sub CheckUsernameLength {

    my $self		= shift;
    my $username	= shift;

    if (!defined $username || $username eq "") {
	# error popup
        return __("No username entered.
Try again.");
    }

    my $min		= $self->GetMinLoginLength ();
    my $max		= $self->GetMaxLoginLength ();

    if (length ($username) < $min || length ($username) > $max) {

	# error popup
	return sprintf (__("The username must be between %i and %i characters in length.
Try again."), $min, $max);
    }
    return "";
}

##------------------------------------
# check given user name for valid contents
# @param user name
# @param user type (local/ldap etc.)
# @return error message
BEGIN { $TYPEINFO{CheckUsernameContents} = ["function",
    "string", "string", "string"];
}
sub CheckUsernameContents {

    my ($self, $username, $type)	= @_;
    my $filtered	= $username;

    # Samba users may need to have '$' at the end of username (#40433)
    if ($type eq "ldap") {
	$filtered =~ s/\$$//g;
    }
    my $grep = SCR->Execute (".target.bash_output", "echo '$filtered' | grep '\^$character_class\$'", { "LANG" => "C" });
    my $stdout = $grep->{"stdout"} || "";
    $stdout =~ s/\n//g;
    if ($stdout ne $filtered) {
	y2warning ("username $username doesn't match to $character_class");
	# error popup
	return __("The username may contain only
letters, digits, \"-\", \".\", and \"_\"
and must begin with a letter or \"_\".
Try again.");
    }
    return "";
}



##------------------------------------
# check given user name for a conflict with a (fixed) set of system users
# @param user name
# @return error message
BEGIN { $TYPEINFO{CheckUsernameConflicts} = ["function", "string", "string" ]; }
sub CheckUsernameConflicts {
    
    my ($self, $username)	= @_;

    if (defined $system_users{$username}) {
	# error popup
	return __("There is a conflict between the entered
username and an existing username.
Try another one.");
    }
}

# return the %system_users map = these are NOT the current system users,
# but the names that could be used for system users by packages
BEGIN { $TYPEINFO{GetSystemUserNames} = ["function", ["map", "string", "integer"]];}
sub GetSystemUserNames {
    return \%system_users;
}

##---------------------------------------------------------------------------

##---------------------------------------------------------------------------
## Read/Write functions

# Writes the settings configured in 1st stage
BEGIN { $TYPEINFO{Write} = ["function", "boolean"];}
sub Write {

    my $self		= shift;
    my $user_defined	= 0;
    foreach my $user (@users) {
	if (defined $user->{"userPassword"}) {
	    if (!defined $user->{"__imported"}) {
		$user->{"userPassword"}	=
		    $self->CryptPassword($user->{"userPassword"});
	    }
	    $user->{"encrypted"}	= YaST::YCP::Integer (1);
	}
	$user_defined	= 1;
    }
    my %data = (
        "after_auth"		=> $after_auth,
	"run_krb_config"	=> YaST::YCP::Integer ($run_krb_config),
        "users"			=> \@users,
	"encryption_method"	=> $encryption_method,
	"root_alias"		=> $root_alias,
	"autologin_user"	=> $autologin_user
    );
    if ($root_password) {
	# indication to inst_root
	$data{"root_password_written"}	= YaST::YCP::Integer (1);
    }
    my $file	= Directory->vardir()."/users_first_stage.ycp";
    my $ret	= SCR->Write (".target.ycp", $file, \%data);

    y2milestone ("1st stage user information written: ", $ret);

    # make the file root only readable
    SCR->Execute (".target.bash", "chmod 600 $file") if ($ret);
 
    my $redraw	= 0;
    if ($root_password) {
	# write root password now
	$self->WriteRootPassword ();
    }
    else {
	y2milestone ("enabling step 'root' for second stage");
	ProductControl->EnableModule ("root");
    }
    # enable inst_user to either run auth client or write first user
    if ($after_auth ne "users" || $user_defined) {
	y2milestone ("enabling step 'user' for second stage");
	if ($user_defined) {
	    ProductControl->EnableModule ("user_non_interactive");
	}
	else {
	    ProductControl->EnableModule ("user");
	}
    }
    # no user entered + 2nd stage visible => enable clients (bnc#393722)
    elsif (!ProductControl->GetUseAutomaticConfiguration ()) {
	y2milestone ("enabling steps 'auth' and 'user' for second stage");
	ProductControl->EnableModule ("auth");
	ProductControl->EnableModule ("user");
    }

    return $ret;
}

# Read the settings configured in 1st stage
BEGIN { $TYPEINFO{Read} = ["function", "boolean", "boolean"];}
sub Read {

    my $self	= shift;
    my $force	= shift;
    my $file	= Directory->vardir()."/users_first_stage.ycp";
    my $ret	= 0;

    if (FileUtils->Exists ($file) && ($force || $first_stage_data_not_read)) {
	my $data	= SCR->Read (".target.ycp", $file);
	$first_stage_data_not_read	= 0;
	if (defined $data && ref ($data) eq "HASH") {

	    $autologin_user	= $data->{"autologin_user"}	|| "";
	    $root_alias		= $data->{"root_alias"}		|| "";
	    $after_auth		= $data->{"after_auth"}		|| $after_auth;
	    $encryption_method	=
		$data->{"encryption_method"} || $encryption_method; 
	    $run_krb_config	= bool ($data->{"run_krb_config"});
	    if (ref ($data->{"users"}) eq "ARRAY") {
		@users		= @{$data->{"users"}};
	    }
	    $root_password_written = bool ($data->{"root_password_written"});
	    $users_written	= bool ($data->{"users_written"});
	    $ret	= 1;
	}
#	SCR->Execute (".target.remove", $file);
    }
    return bool ($ret);
}

# Remove the private data from users_first_stage.ycp after they were
# written (bnc#395796)
BEGIN { $TYPEINFO{RemoveUserData} = ["function", "boolean"];}
sub RemoveUserData {

    my $self	= shift;
    my $file	= Directory->vardir()."/users_first_stage.ycp";
    my $ret	= 1;

    if (FileUtils->Exists ($file)) {
	my $data	= SCR->Read (".target.ycp", $file);
	if (defined $data && ref ($data) eq "HASH") {
	    if (defined $data->{"users"}) {
		delete $data->{"users"};
	    }
	    $data->{"users_written"}	= YaST::YCP::Integer (1);
	    $ret	= SCR->Write (".target.ycp", $file, $data);
	}
    }
    return bool ($ret);
}

##---------------------------------------------------------------------------
## functions for handling passwd/shadow files in the 1st stage
## (simplified version of functions from UsersPasswd and Users)


# read 'shadow' file from a given directory
# return hash with shadow description
sub read_shadow {

    my $base_directory	= shift;
    my $file		= "$base_directory/shadow";
    my %shadow_tmp	= ();
    my $in		= SCR->Read (".target.string", $file);

    if (! defined $in) {
	y2warning ("$file cannot be opened for reading!");
	return undef;
    }

    foreach my $shadow_entry (split (/\n/,$in)) {
	chomp $shadow_entry;
	next if ($shadow_entry eq "");

	my ($uname,$pass,$last_change,$min, $max, $warn, $inact, $expire, $flag)
	    = split(/:/,$shadow_entry);  
        my $first = substr ($uname, 0, 1);

	if ($first ne "#" && $first ne "+" && $first ne "-")
	{
	    if (!defined $uname || $uname eq "") {
		y2error ("strange line in shadow file: '$shadow_entry'");
		return undef;
	    }
	    if (defined $shadow_tmp{$uname})
	    {
		y2error ("duplicated username in /etc/shadow! Exiting...");
		return undef;
	    }
	    $shadow_tmp{$uname} = {
		"shadowLastChange"	=> $last_change,
		"shadowWarning"		=> $warn,
		"shadowInactive"	=> $inact,
		"shadowExpire"		=> $expire,
		"shadowMin"		=> $min,
		"shadowMax"		=> $max,
		"shadowFlag"		=> $flag,
		"userPassword"		=> $pass
	    };
	}
    }
    return \%shadow_tmp;
}

# read content of 'passwd' file under given directory
# - save data into internal structure
# return boolean (success)
sub read_passwd {

    my $base_directory	= shift;
    my $shadow_tmp	= shift;
    my $file		= "$base_directory/passwd";

    %imported_users 		= ();
    %imported_shadow		= ();
    my %usernames		= ();

    my $in	= SCR->Read (".target.string", $file);
    if (! defined $in) {
	y2warning ("$file cannot be opened for reading!");
	return 0;
    }

    foreach my $user (split (/\n/,$in)) {
	chomp $user;
	next if ($user eq "");

	my ($username, $password, $uid, $gid, $full, $home, $shell)
	    = split(/:/,$user);
        my $first = substr ($username, 0, 1);

	if ($first ne "#" && $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'");
		return 0;
	    }
		
            my $user_type	= "local";

	    if (($uid <= $max_system_uid) || ($username eq "nobody")) {
		$user_type = "system";
	    }
    
	    my $colon = index ($full, ",");
	    my $additional = "";
	    if ( $colon > -1)
	    {
		$additional = $full;
		$full = substr ($additional, 0, $colon);
		$additional = substr ($additional, $colon + 1,
		    length ($additional));
	    }
	    
	    if (defined $usernames{"local"}{$username} ||
		defined $usernames{"system"}{$username})
	    {
		y2error ("duplicated username in /etc/passwd! Exiting...");
		return 0;
	    }
	    else
	    {
		$usernames{$user_type}{$username} = 1;
	    }
    
	    # such map we would like to export from the read script...
	    $imported_users{$user_type}{$username} = {
		"addit_data"	=> $additional,
		"cn"		=> $full,
		"homeDirectory"	=> $home,
		"uid"		=> $username,
		"uidNumber"	=> $uid,
		"gidNumber"	=> $gid,
		"loginShell"	=> $shell,
	    };
	    if (defined $shadow_tmp->{$username}) {
		# divide shadow map accoring to user type
		$imported_shadow{$user_type}{$username} =
		    $shadow_tmp->{$username};
	    }
	}
    }
    return 1;
}

##------------------------------------
# Read passwd and shadow files in 1st stage of the installation
# string parameter is path to directory with passwd, shadow files
BEGIN { $TYPEINFO{ReadUserData} = ["function", "boolean", "string"]; }
sub ReadUserData {

    my ($self, $base_directory)	= @_;
    my $ret			= 0;
    my $shadow_tmp	= read_shadow ($base_directory);
    if (defined $shadow_tmp && ref ($shadow_tmp) eq "HASH") {
	$ret	= read_passwd ($base_directory, $shadow_tmp);
    }
    return $ret;
}

##------------------------------------
# returns hash with imported users of given type
# @param user type
BEGIN { $TYPEINFO{GetImportedUsers} = [
    "function", ["map", "string", "any"], "string"];
}
sub GetImportedUsers {

    my ($self, $type)	= @_;
    my %ret		= ();

    if (defined $imported_users{$type} && ref($imported_users{$type}) eq "HASH")
    {
	%ret 	= %{$imported_users{$type}};
	next if (!defined $imported_shadow{$type});
	# add the shadow data into each user map
	foreach my $username (keys %ret) {
	    next if (!defined $imported_shadow{$type}{$username});
	    foreach my $key (keys %{$imported_shadow{$type}{$username}}) {
	      $ret{$username}{$key} = $imported_shadow{$type}{$username}{$key};
	    }
	}
    }
    return \%ret;
}

##------------------------------------
# Check if importing user data from the system is available:
# if yes, go and read the data
BEGIN { $TYPEINFO{ImportAvailable} = ["function", "boolean"]; }
sub ImportAvailable {

    return $import_available if defined $import_available;

    my $self		= shift;
    my $tmp_dir		= Directory->tmpdir()."/users";
    my $vardir		= Directory->vardir();
    my $import_dir	= $tmp_dir.$vardir."/imported/userdata/etc";

    $import_available	= (
	SCR->Execute (".target.mkdir", $tmp_dir) &&
	SystemFilesCopy->CopyFilesToSystem ($tmp_dir) &&
	FileUtils->Exists ($import_dir."/passwd") &&
	FileUtils->Exists ($import_dir."/shadow") &&
	$self->ReadUserData ($import_dir)
    );
    y2milestone ("import available: $import_available");

    # remove the directory with copied data after reading it
    SCR->Execute (".target.bash", "/bin/rm -rf $tmp_dir");

    return $import_available;
}

##------------------------------------
# check if LDAP/Kerberos are available (fate#301340)
BEGIN { $TYPEINFO{CheckNetworkMethodsAvailability} = ["function", "boolean"]; }
sub CheckNetworkMethodsAvailability {
     
    my $self	= shift;

    return $network_methods_checked if $network_methods_checked;

    # no check during firstboot (bnc#458468)
    if (Stage->firstboot ()) {
	$network_methods_checked	= 1;
	y2milestone ("firstboot stage: no check for network methods");
	return 1;
    }

    if (!NetworkService->isNetworkRunning()) {
	y2milestone ("network is not running, skipping network methods test");
	return 0;
    }

    my $call_extend	= Stage->initial () && !Mode->live_installation ();

    if ($call_extend && !InstExtensionImage->LoadExtension ("bind",
	# busy popup message, %1 is package name
	sformat (__("Retrieving %1 extension..."), "bind")))
    {
	y2error ("loading bind failed, check for network methods skipped");
	return 0;
    }

    my $domain	= Hostname->CurrentDomain ();
    y2milestone ("current domain : '$domain'");

    if (!$domain && Stage->cont ())
    {
	my $out = SCR->Execute (".target.bash_output", "domainname");
	if ($out->{"exit"} eq 0) {
	    $domain	= $out->{"stdout"};
	    chomp $domain;
	    y2milestone ("current domain from domainname: '$domain'");
	}
    }

    # First, check if LDAP server is available
    my $out = SCR->Execute (".target.bash_output", "dig '_ldap._tcp.$domain' SRV +short");
    y2debug ("dig output: ", Dumper ($out));
    my $ldap_server	= "";
    if ($out->{"exit"} eq 0) {
	foreach my $line (split (/\n/,$out->{"stdout"} || "")) {
	    y2debug ("line: $line");
	    if ($line !~ m/^;/) {
		$ldap_server	= (split (/[ \t]/, $line))[3] || ".";
		chop $ldap_server;
		y2debug ("proposed LDAP server '$ldap_server'");
	    }
	    last if $ldap_server ne "";
	}
    }
    if ($ldap_server ne "")
    {
	y2milestone ("LDAP server found, proposing LDAP authentication");
	$self->SetAfterAuth ("ldap");
    }

    # check if AD server is available
    $out = SCR->Execute (".target.bash_output", "dig '_ldap._tcp.dc._msdcs.$domain' SRV +short");
    y2debug ("dig output: ", Dumper ($out));
    my $ad_server	= "";
    if ($out->{"exit"} eq 0) {
	foreach my $line (split (/\n/,$out->{"stdout"} || "")) {
	    y2debug ("line: $line");
	    if ($line !~ m/^;/) {
		$ad_server	= (split (/[ \t]/, $line))[3] || ".";
		chop $ad_server;
		y2debug ("proposed AD server '$ad_server'");
	    }
	    last if $ad_server ne "";
	}
    }
    if ($ad_server ne "")
    {
	y2milestone ("AD server found, proposing winbind authentication");
	$self->SetAfterAuth ("samba");
	$network_methods_checked	= 1;
	# no check for kerberos when AD is detected
	return 1;
    }
    # check for eDirectory now
    elsif ($ldap_server) {
	$out	= SCR->Execute (".target.bash_output", "ldapsearch -x -h $ldap_server -s base -b '' vendorName | grep -i '^vendorName: Novell'");
	y2debug ("ldapsearch output: ", Dumper ($out));
	if ($out->{"exit"} eq 0) {
	    y2milestone ("eDirectory found, proposing for authentication");
	    $self->SetAfterAuth ("edir_ldap");
	}
    }
	 
    # Now, check if kerberos is available
    $out = SCR->Execute (".target.bash_output", "dig '_kerberos._udp.$domain' SRV +short");
    y2debug ("dig output: ", Dumper ($out));
    my $kdc	= "";
    if ($out->{"exit"} eq 0) {
	foreach my $line (split (/\n/,$out->{"stdout"} || "")) {
	    y2debug ("line: $line");
	    if ($line !~ m/^;/) {
		$kdc	= (split (/[ \t]/, $line))[3] || ".";
		chop $kdc;
	    }
	    last if $kdc ne "";
	}
    }
    if ($kdc ne "" && $ldap_server ne "")
    {
	y2milestone ("KDC found, proposing Kerberos authentication");
	$self->SetKerberosConfiguration (1);
    }
    $network_methods_checked	= 1;

    if ($call_extend)
    {
	InstExtensionImage->UnLoadExtension ("bind",
	    # busy popup message, %1 is package name
	    sformat (__("Releasing %1 extension..."), "bind"));
    }

    return 1;
}

##------------------------------------
# load cracklib image into the inst-sys
BEGIN { $TYPEINFO{LoadCracklib} = ["function", "boolean"]; }
sub LoadCracklib {

    if (!Stage->initial () || Mode->live_installation ()) {
	y2debug ("no extend in this stage/mode");
	return 1;
    }
    return InstExtensionImage->LoadExtension ("cracklib-dict-full.rpm",
	# busy popup message
	sformat (__("Retrieving %1 extension..."), "cracklib-dict-full.rpm"));
}

##------------------------------------
# release cracklib image from the inst-sys
BEGIN { $TYPEINFO{UnLoadCracklib} = ["function", "boolean"]; }
sub UnLoadCracklib {
	
    if (!Stage->initial () || Mode->live_installation ()) {
	y2debug ("no extend in this stage/mode");
	return 1;
    }
    return InstExtensionImage->UnLoadExtension ("cracklib-dict-full.rpm",
	# busy popup message
	sformat (__("Releasing %1 extension..."), "cracklib-dict-full.rpm"));
}

##------------------------------------
# use iconv transliteration feature to convert special characters to similar
# ASCII ones (bnc#442225)
BEGIN { $TYPEINFO{Transliterate} = ["function", "string", "string"]; }
sub Transliterate {

    my ($self, $text)	= @_;

    return "" if ! $text;
    my $language	= Language->language ();
    my $out = SCR->Execute (".target.bash_output",
	"echo '$text' | iconv -f utf-8 -t ascii//translit",
	{ "LANG" => $language });
    my $stdout = $out->{"stdout"} || "";
    return $stdout;
}

42
# EOF

ACC SHELL 2018