#! /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->{'sambaNTPassword'}) &&
              ( (! $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'}) &&
	 (! $data->{'sambaNTPassword'}) ) {
        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
