ACC SHELL
#! /usr/bin/perl -w
#
# Samba plugin module
# This is the API part of UsersPluginSamba plugin
#
# The following user parameters are handled inside this module
#
package UsersPluginSamba;
use strict;
use ycp;
use YaST::YCP;
use YaPI;
our %TYPEINFO;
## FIXME
use Data::Dumper;
use Crypt::SmbHash;
YaST::YCP::Import ("SCR");
textdomain("samba-users"); # TODO own textdomain for new plugins
##--------------------------------------
##--------------------- global imports
YaST::YCP::Import ("SCR");
##--------------------------------------
# All functions have 2 "any" parameters: this will probably mean
# 1st: configuration map (hash) - e.g. saying if we work with user or group
# 2nd: data map (hash) of user (group) to work with
# in 'config' map there is a info of this type:
# "what" => "user" / "group"
# "modified" => "added"/"edited"/"deleted"
# "enabled" => 1/ key not present
# "disabled" => 1/ key not present
# 'data' map contains the atrtributes of the user. It could also contain
# some keys, which Users module uses internaly (like 'groupname' for name of
# user's default group). Just ignore these values
# default object classes of LDAP users
##------------------------------------
my $pluginName = "UsersPluginSamba";
my $error = "";
# return names of provided functions
BEGIN { $TYPEINFO{Interface} = ["function", ["list", "string"], "any", "any"];}
sub Interface {
my $self = shift;
my @interface = (
"GUIClient",
"Check",
"Name",
"Summary",
"Restriction",
"Add",
"AddBefore",
"Edit",
"EditBefore",
"Interface",
"PluginPresent",
"Disable",
"InternalAttributes",
"Error"
);
return \@interface;
}
# return plugin name, used for GUI (translated)
BEGIN { $TYPEINFO{Name} = ["function", "string", "any", "any"];}
sub Name {
my $self = shift;
# plugin name
return __("Samba Attributes");
}
# return plugin summary
BEGIN { $TYPEINFO{Summary} = ["function", "string", "any", "any"];}
sub Summary {
my $self = shift;
my $what = "user";
# summary
my $ret = __("Manage samba account parameters");
return $ret;
}
# return name of YCP client defining YCP GUI
BEGIN { $TYPEINFO{GUIClient} = ["function", "string", "any", "any"];}
sub GUIClient {
my $self = shift;
return "users_plugin_samba";
}
##------------------------------------
# Type of users and groups this plugin is restricted to.
# If this function doesn't exist, plugin is applied for all user (group) types.
BEGIN { $TYPEINFO{Restriction} = ["function",
["map", "string", "any"], "any", "any"];}
sub Restriction {
my $self = shift;
# this plugin applies only for LDAP users and groups
return { "ldap" => 1,
"user" => 1 };
}
# return error message, generated by plugin
BEGIN { $TYPEINFO{Error} = ["function", "string", "any", "any"];}
sub Error {
my $ret = $error;
$error = "";
return $ret;
}
# checks the current data map of user/group (2nd parameter) and returns
# true if given user (group) has our plugin
BEGIN { $TYPEINFO{PluginPresent} = ["function", "boolean", "any", "any"];}
sub PluginPresent {
my $self = shift;
my $config = shift;
my $data = shift;
if ( grep /^sambasamaccount$/i, @{$data->{'objectClass'}} ) {
y2milestone( "SambaPlugin: Plugin Present");
return 1;
} else {
y2milestone( "SambaPlugin: Plugin not Present");
return 0;
}
}
##------------------------------------
# check if all required atributes of LDAP entry are present
# parameter is (whole) map of entry (user/group)
# return error message
BEGIN { $TYPEINFO{Check} = ["function",
"string",
"any",
"any"];
}
sub Check {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
return "";
}
BEGIN { $TYPEINFO{Disable} = ["function",
["map", "string", "any"],
"any", "any"];
}
sub Disable {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
y2milestone ("Disable Samba called");
return $data;
}
# Could be called multiple times for one user/group!
BEGIN { $TYPEINFO{AddBefore} = ["function", ["map", "string", "any"], "any", "any"];}
sub AddBefore {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
y2milestone ("AddBefore Samba called");
if( (! $data->{'sambainternal'}->{'initialized'}) ||
($data->{'sambainternal'}->{'initialized'} != 1) ) {
my $ret = $self->init_samba_sid( $config, $data );
if( $ret ) {
if ( $data->{'uid'} && $data->{'uid'} ne "" ) {
y2internal("Could not initialize samba sid");
return $data;
} else {
return $data;
}
} else {
y2milestone("initialized");
$data->{'sambainternal'}->{'initialized'} = 1;
}
}
my $ret = $self->update_object_classes( $config, $data );
if( $ret ) {
y2internal("Could not update objectClass attribute");
$error = __("Could not update objectClass attribute.");
return undef;
}
return $data;
}
# This will be called just after Users::Add - the data map probably contains
# the values which we could use to create new ones
# Could be called multiple times for one user/group!
BEGIN { $TYPEINFO{Add} = ["function", ["map", "string", "any"], "any", "any"];}
sub Add {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
y2milestone ("Add Samba called");
# Has the plugin been removed?
if( grep /^$pluginName$/, @{$data->{'plugins_to_remove'}} ) {
$self->remove_plugin_data( $config, $data );
y2debug ("Removed Samba plugin");
y2debug ( Data::Dumper->Dump( [ $data ] ) );
return $data;
}
if( $data->{'sambainternal'}->{'initialized'} &&
$data->{'sambainternal'}->{'initialized'} == 1 ) {
$self->update_attributes ($config, $data);
}
y2debug ( Data::Dumper->Dump( [ $data ] ) );
return $data;
}
BEGIN { $TYPEINFO{EditBefore} = ["function",
["map", "string", "any"],
"any", "any"];
}
sub EditBefore {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
y2internal ("EditBefore Samba called");
# First time call to Edit()
if( ! $data->{'sambainternal'} ) {
$data->{'sambainternal'} = {};
my $ret = $self->init_samba_sid( $config, $data );
if( $ret ) {
y2internal("Could not initialize Samba SID");
$error = __("Could not initialize Samba SID. Disabling plug-in.");
my @updated_plugin;
foreach my $plugin ( @{$data->{'plugin'}} ) {
if ( lc($plugin) ne $pluginName ) {
push @updated_plugin, $plugin;
}
}
$data->{'plugin'} = \@updated_plugin;
return undef;
}
}
y2debug ( Data::Dumper->Dump( [ $data ] ) );
return $data;
}
# this will be called at the beggining of Users::Edit
BEGIN { $TYPEINFO{Edit} = ["function",
["map", "string", "any"],
"any", "any"];
}
sub Edit {
my $self = shift;
my $config = $_[0];
my $data = $_[1];
y2milestone ("Edit Samba called");
# Has the plugin been removed?
if( grep /^$pluginName$/, @{$data->{'plugins_to_remove'}} ) {
$self->remove_plugin_data( $config, $data );
y2debug ("Removed Samba plugin");
y2debug ( Data::Dumper->Dump( [ $data ] ) );
return $data;
}
if( ! $data->{'sambainternal'}->{'initialized'} ) {
$self->init_internal_keys( $config, $data );
$data->{'sambainternal'}->{'initialized'} = 1;
} elsif ( (! $data->{'sambaLMPassword'}) &&
( (! $data->{'text_userpassword'} ) || ($data->{'text_userpassword'} eq "" )) ){
$error = __("Change the password to create the Samba account");
return undef;
}
# If user doesn't have a Samba Account yet some initialization
# has to take place now.
if ( ! $self->PluginPresent( $config, $data ) ) {
$self->update_object_classes( $config, $data );
}
$self->update_attributes ($config, $data);
if ( (! $data->{'sambaLMPassword'}) ) {
y2debug ("no samba password hashes present yet");
}
y2debug ( Data::Dumper->Dump( [ $data ] ) );
return $data;
}
# this will be called at the beggining of Users::Edit
BEGIN { $TYPEINFO{InternalAttributes} = ["function",
["map", "string", "any"],
"any", "any"];
}
sub InternalAttributes {
return [ "sambainternal", "sambanoexpire", "sambadisabled"];
}
sub update_object_classes {
my ($self, $config, $data) = @_;
my $oc = "sambaSamAccount";
# define the object class for new user/groupa
if (defined $data->{"objectClass"} && ref $data->{"objectClass"} eq "ARRAY")
{
if ( ! grep /^$oc$/i, @{$data->{'objectClass'}} ) {
push @{$data->{'objectClass'}}, $oc;
y2milestone("added ObjectClass $oc");
}
}
return undef;
}
sub init_internal_keys {
my ($self, $config, $data) = @_;
if ( $data->{'sambaAcctFlags'} ) {
if ( ! defined( $data->{'sambadisabled'} ) ) {
y2internal(" UsersPluginSamba::init_internal_keys sambadisabled undefined ");
if ( $data->{'sambaAcctFlags'} =~ /^\[.*D.*\]/ ) {
$data->{'sambadisabled'} = "1";
} else {
$data->{'sambadisabled'} = "0";
}
}
if ( ! defined( $data->{'sambanoexpire'} ) ) {
y2internal(" UsersPluginSamba::init_internal_keys sambanoexpire undefined ");
if ( $data->{'sambaAcctFlags'} =~ /^\[.*X.*\]/ ) {
$data->{'sambanoexpire'} = "1";
} else {
$data->{'sambanoexpire'} = "0";
}
}
}
return undef;
}
sub update_attributes {
my ( $self, $config, $data ) = @_;
my $SID = $data->{'sambainternal'}->{'sambalocalsid'};
my $uidNumber = $data->{'uidNumber'};
if ( $uidNumber ) {
if ( (! $data->{'sambaSID'}) || ($data->{'sambaSID'} eq "") ) {
$data->{'sambaSID'} = $SID."-". ( 2 * $uidNumber +
$data->{'sambainternal'}->{'ridbase'} );
}
}
my $gidNumber = $data->{'gidNumber'};
if ( $gidNumber ) {
if ( (! $data->{'sambaPrimaryGroupSID'}) ||
($data->{'sambaPrimaryGroupSID'} eq "") ) {
$data->{'sambaPrimaryGroupSID'} = $SID."-". (2 * $gidNumber +
$data->{'sambainternal'}->{'ridbase'} + 1 );
}
}
$data->{'sambainternal'}->{'sambacleartextpw'} = $data->{'text_userpassword'};
my $ret = $self->update_samba_pwhash( $config, $data );
if( $ret ) {
return $ret;
}
$ret = $self->update_samba_acctflags( $config, $data );
if( $ret ) {
return $ret;
}
}
sub update_samba_acctflags {
my ($self, $config, $data) = @_;
my $acctflags = $data->{'sambaAcctFlags'} || "[U ]";
$acctflags =~ s/^\[(\w+)\s*\]$/$1/g;
if( defined( $data->{'sambadisabled'} ) &&
$data->{'sambadisabled'} eq "1" ) {
if ( $acctflags !~ /D/ ) {
$acctflags .= "D";
}
} elsif ( (! defined( $data->{'sambadisabled'}))
|| $data->{'sambadisabled'} eq "0" ) {
$acctflags =~ s/^(.*)D(.*)$/$1$2/g;
}
if( defined( $data->{'sambanoexpire'} )
&& $data->{'sambanoexpire'} eq "1" ) {
if ( $acctflags !~ /X/ ) {
$acctflags .= "X";
}
} elsif ( (! defined( $data->{'sambanoexipre'}))
|| $data->{'sambanoexpire'} eq "0" ) {
$acctflags =~ s/^(.*)X(.*)$/$1$2/g;
}
my $len = length($acctflags);
for( my $i=0; $i < ( 11 - $len ); $i++ ) {
$acctflags .= " ";
}
$data->{'sambaAcctFlags'} = "[". $acctflags ."]";
return undef;
}
sub update_samba_pwhash {
my ( $self, $config, $data ) = @_;
if ( $data->{'sambainternal'}->{'sambacleartextpw'} ) {
my $update_timestamp = 0;
my ($lmHash, $ntHash) = ntlmgen($data->{'sambainternal'}->{'sambacleartextpw'});
if ( (!$data->{'sambaLMPassword'}) || ($lmHash ne $data->{'sambaLMPassword'}) ) {
$data->{'sambaLMPassword'} = $lmHash;
$update_timestamp = 1;
}
if ( (! $data->{'sambaNTPassword'}) || ( $ntHash ne $data->{'sambaNTPassword'} ) ) {
$data->{'sambaNTPassword'} = $ntHash;
$update_timestamp = 1;
}
if ( $update_timestamp ) {
$data->{'sambaPwdLastSet'} = time ();
$data->{'sambaPwdCanChange'} = $data->{'sambaPwdLastSet'};
}
$data->{'sambaPwdMustChange'} = ( 1 << 31 ) - 1;
}
return undef;
}
sub init_samba_sid {
my ( $self, $config, $data ) = @_;
if ( (! $data->{'sambainternal'}->{'sambalocalsid'}) ||
($data->{'sambainternal'}->{'sambalocalsid'} eq "") ) {
my $base_dn = Ldap->GetDomain();
my $res = SCR->Read(".ldap.search", { base_dn => $base_dn,
scope => YaST::YCP::Integer(2),
filter => "(objectClass=sambaDomain)",
attrs => ['sambaSID', 'sambaAlgorithmicRidBase']
}
);
if ( ! $res ){
y2internal( "LDAP Error" );
my $ldaperr = SCR::Read(".ldap.error" );
y2internal("$ldaperr->{'code'}");
y2internal("$ldaperr->{'msg'}");
return "error reading samba sid";
} else {
#y2milestone( Data::Dumper->Dump( [$res] ));
if ( $res->[0]->{'sambaSID'}->[0] ) {
$data->{'sambainternal'}->{'sambalocalsid'} = $res->[0]->{'sambaSID'}->[0];
$data->{'sambainternal'}->{'ridbase'} = $res->[0]->{'sambaAlgorithmicRidBase'}->[0];
return undef;
} else {
return "error reading samba sid";
}
}
}
}
sub remove_plugin_data {
my ( $self, $config, $data ) = @_;
my @updated_oc;
foreach my $oc ( @{$data->{'objectClass'}} ) {
if ( lc($oc) ne "sambasamaccount" ) {
push @updated_oc, $oc;
}
}
delete( $data->{'sambanoexpire'});
delete( $data->{'sambadisabled'});
delete( $data->{'sambainternal'});
$data->{'objectClass'} = \@updated_oc;
}
1;
# EOF
ACC SHELL 2018