summaryrefslogblamecommitdiffstats
path: root/src/os-plugins/plugins/auth/OpenSLX/OSPlugin/auth.pm
blob: 939223f1feb30af428f44bbd07ae44c256f61b52 (plain) (tree)


































































































































































































































































































                                                                                                                                                                                         
                                                                           




                                                        
                                                                               







                                   
 



                                                      
                                                                                                    

                   























                                                                                                           





                                                                                                            
                                                                                                                


                                                          
                                                                                       

                   


























                                                                                    




                                                                 











                                                                           











































                                                                             
                                                                                                         


                                          
                                                                                                              


                                                               
                                                                                                               


                                                                             
                                                                                                

































































































































                                                                                         


                                                                                                



               
                                         
















                                                                                                                 
                                                                                                                                            






























                                                                                                       
# Copyright (c) 2010 - OpenSLX GmbH
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
package OpenSLX::OSPlugin::auth;

use strict;
use warnings;

use base qw(OpenSLX::OSPlugin::Base);

use OpenSLX::Basics;
use OpenSLX::Utils;

sub new
{
    my $class = shift;

    my $self = {
        name => 'auth',
    };

    return bless $self, $class;
}

sub getInfo
{
    my $self = shift;

    return {
        description => unshiftHereDoc(<<'        End-of-Here'),
            auth plugin to configure desired authentification
        End-of-Here
        precedence => 50,
    };
}

sub getAttrInfo
{   # returns a hash-ref with information about all attributes supported
    # by this specific plugin
    my $self = shift;

    # This default configuration will be added as attributes to the default
    # system, such that it can be overruled for any specific system by means
    # of slxconfig.
    return {
        # attribute 'active' is mandatory for all plugins
        'auth::active' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                should the 'auth'-plugin be executed during boot?
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '1',
        },

        # plugin specific attributes start here ...
        'auth::files' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Folder of extra files. Default: /root/auth-plugin
                Optional, except with kerberOS for krb5.conf!
            End-of-Here
            content_regex => qr{^/.*$},
            content_descr => 'Path starting with /',
            default => '/root/auth-plugin',
        },

        'auth::passwd' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Should the /etc/{passwd,shadow} authentification be used?
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '1',
        },

        'auth::rootpwd' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                If set, defines the root-password
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'Defines root-password. "" if not set.',
            default => '',
        },

        'auth::ldap' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Should we use ldap for authentification? NSS for lookup.
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '0',
        },
        'auth::ldapuri' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                LDAP URI. Example: "ldap://server1 ldap://server2 ldap://server3"
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'ldap(s)://serverX (ldap(s)://serverY ...)',
            default => '',
        },
        'auth::ldapbase' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                LDAP base distinguished name. Example: "ou=people,dc=domain,dc=de"
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'ou=people,dc=domain,dc=de',
            default => '',
        },
        'auth::nss_base_passwd' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                specify the search base, scope and filter for passwd. Example: "ou=people,dc=domain,dc=de?one?foo=*"
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => '<basedn?scope?filter>',
            default => '',
        },
        'auth::nss_base_group' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                specify the search base, scope and filter for group. Example: "ou=people,dc=domain,dc=de?one?foo=*"
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => '<basedn?scope?filter>',
            default => '',
        },
        'auth::ldap_options' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                extra nss_ldap or ldap options. Example: "nss_map_attribute <from_attribute> <to_attribute>". Multiple options can be seperated by "<option1>\n<option2>\n<option3>\n..."
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'see nss_ldap(5)',
            default => '',
        },
        'auth::ldapbinddn' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                specify the dn to bind to. Example: "ou=people,dc=domain,dc=de?one?foo=*"
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'see nss_ldap(5)',
            default => '',
        },
        'auth::ldapbindpw' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                specify the dn password to bind to.
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'see nss_ldap(5)',
            default => '',
        },
        'auth::automount' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Should we use automount?
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '0',
        },
        'auth::automnt_src' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                give the source where we get the automount fs from. E.g.
                nfs://x.x.x.x/home
            End-of-Here
            content_regex => qr{^(|nfs://.*)$},
            content_descr => 'currently only nfs://host/path possible',
            default => '',
        },
        'auth::automnt_dir' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Where should we mount the data to?
                E.g.: /home/nfs/
            End-of-Here
            content_regex => qr{^(\/.*|)$},
            content_descr => 'Has to start with /',
            default => '',
        },
        'auth::automnt_script' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Automount will use a script and not automnt_dir. This script has to
                be in path of auth::files
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'scriptname',
            default => '',
        },
        'auth::nfs4' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Do you want to use NFS4? For example with automount.
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '0',
        },
        'auth::idmap_domain' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                What should be the domain used for idmapd?
                Usually needed with NFS4
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'Domainname like mydomain.tld',
            default => '',
        },
        'auth::krb' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                Do you want to use KerberOS? Default is off
                If enabled, you must provide krb5.conf in the path defined
                in auth::files (default: /root/auth-plugin)
            End-of-Here
            content_regex => qr{^(0|1)$},
            content_descr => '1 means active - 0 means inactive',
            default => '0',
        },
        'auth::krbscript' => {
            applies_to_systems => 1,
            applies_to_clients => 1,
            description => unshiftHereDoc(<<'            End-of-Here'),
                KerberOS: script to run to do extra kerberOS configuration
                  like getting a credential over network. Optional feature.
                  The given script will be started through /etc/init.d/boot.slx
                  in Stage4.
            End-of-Here
            content_regex => qr{^.*$},
            content_descr => 'filename of script which needs to be in auth:krbconf-Directory',
            default => '',
        },

    };
}

# called while chrooted to the vendor-OS root in order to give the plugin
# a chance to install required files into the vendor-OS.
sub installationPhase
{   
    my $self = shift;
    my $info = shift;
    
    $self->{pluginRepositoryPath} = $info->{'plugin-repo-path'};
    $self->{pluginTempPath}       = $info->{'plugin-temp-path'};
    $self->{openslxBasePath}      = $info->{'openslx-base-path'};
    $self->{openslxConfigPath}    = $info->{'openslx-config-path'};
    $self->{attrs}                = $info->{'plugin-attrs'};

    my $engine = $self->{'os-plugin-engine'};

    my $ldap = $self->{attrs}->{'auth::ldap'};
    my $automount = $self->{attrs}->{'auth::automount'};
    my $nfs4 = $self->{attrs}->{'auth::nfs4'};
    my $passwd = $self->{attrs}->{'auth::passwd'};
    my $krb = $self->{attrs}->{'auth::krb'};
    my $distro = (split('-',$self->{'os-plugin-engine'}->distroName()))[0];

    # configure passwd
    if($passwd) {
        my $rootPwd = $self->{attrs}->{'auth::rootpwd'};
        if($rootPwd eq "") {
            print "* root-password not set. Change auth::rootpwd and retry.\n";
            exit 1;
        }
        
        $self->_setPasswordForUser(
            'root', "$rootPwd"
        );
    }


    # configure ldap
    if ($ldap) {
        if ($self->{attrs}->{'auth::ldapuri'} eq '' ||
          $self->{attrs}->{'auth::ldapbase'} eq '') {
            print "* auth::ldapuri and/or auth::ldapbase not defined. LDAP configuration canceld\n";
            exit 1;
        }
        if ($distro eq 'ubuntu' && ! -d "/usr/share/doc/libpam-ldap") {
            if (! -d "/usr/share/doc/libpam-ldapd") {
                # ubuntu 10.04 && 11.04
                print "* libpam-ldapd or libpam-ldap not installed but required.\n";
                print "  libpam-ldap is preferred over libpam-ldapd!\n";
                print "  Please install one of these packages and try again!\n";
                exit 1;
            }
            print "* libpam-ldapd installed but not recommended. If there are problems, be aware about\n";
            print "  /etc/nslcd.conf which can differ from /etc/pam.conf syntax!\n";
            print "  /etc/nslcd.conf can also be copied via auth::files\n";
        }
        if ($distro eq 'suse' && ! -d "/usr/share/doc/packages/pam_ldap") {
            if (! -d "/usr/share/doc/packages/nss-pam-ldapd") {
                # suse 10.4
                print "* pam_ldap or nss-pam-ldapd not installed but required.\n";
                print "  pam_ldap is preferred over nss-pam-ldapd!\n";
                print "  Please install one of these packages and try again!\n";
                exit 1;
            }
            print "* nss-pam-ldapd installed but not recommended. If there are problems, be aware about\n";
            print "  /etc/nslcd.conf which can differ from /etc/pam.conf syntax!\n";
            print "  /etc/nslcd.conf can also be copied via auth::files\n";
        }
        $self->_writeLdapConf(); #write ldap.conf
    }

    # configure automount
    if ($automount) {
        if ($self->{attrs}->{'auth::automnt_src'} eq '' && $self->{attrs}->{'auth::automnt_script'} eq '') {
            print "* auth::automnt_src and auth::automnt_script not defined. Automount configuration canceld\n";
            exit 1;
        }
        if ($self->{attrs}->{'auth::automnt_dir'} eq '') {
            print "* auth::automnt_dir not defined. Automount configuration canceld\n";
            exit 1;
        }
        if ($distro eq 'ubuntu') {
            #both package names: ubuntu 10.04&11.04
            if ( ! -d "/usr/share/doc/nfs-common") {
                print "* Package nfs-common not installed, but is required.\n";
                print "  Please install first.\n";
                exit 1;
            }
            if (! -d  "/usr/share/doc/autofs5") {
                print "* Package autofs/autofs5 not installed, but are required.\n";
                print "  Please install first.\n";
                exit 1;
            }
        }
        if ($distro eq 'suse') {
            if (! -f "/etc/init.d/nfs") {
                # suse 11.4 (no ../doc file, but testable through init.d file)
                print "* Package nfs-client not installed, but are required.\n";
                print "  Please install first.\n";
                exit 1;
            }
            if (! -d  "/usr/share/doc/packages/autofs") {
                # suse 11.4
                print "* Package autofs not installed, but are required.\n";
                print "  Please install first.\n";
                exit 1;
            }
        }
        $self->_writeAutomountConf();
    }
    
    # configure kerberOS. Biggest part in preInstallationPhase()!
    if ($krb) {
        if ($distro eq 'ubuntu' && ! -d "/usr/share/doc/libpam-krb5") {
            #ubuntu 11.04&10.04
            print "* Package libpam-krb5 not installed but required.\n";
            print "  Please install first!\n";
            exit 1;
        }
        if ($distro eq 'suse' && ! -d "/usr/share/doc/packages/pam_krb5") {
            #ubuntu 11.04&10.04
            print "* Package pam_krb5 not installed but required.\n";
            print "  Please install first!\n";
            exit 1;
        }
        $self->_krbConf();
    }
            
    return;
}

sub removalPhase
{   # called while chrooted to the vendor-OS root in order to give the plugin
    # a chance to uninstall no longer required files from the vendor-OS.
    my $self = shift;
    my $info = shift;
    
    my $pluginRepoPath = $info->{'plugin-repo-path'};
        # The folder where the stage1-plugin should store all files
        # required by the corresponding stage3 runlevel script.
        # As this method is being executed while chrooted into the vendor-OS,
        # this path is relative to that root (i.e. directly usable).
    my $pluginTempPath = $info->{'plugin-temp-path'};
        # A temporary playground that will be cleaned up automatically.
        # As this method is being executed while chrooted into the vendor-OS,
        # this path is relative to that root (i.e. directly usable).

    return;
}

sub preInstallationPhase()
{   
    my $self = shift;
    my $info = shift;

    $self->{pluginRepositoryPath} = $info->{'plugin-repo-path'};
    $self->{pluginTempPath}       = $info->{'plugin-temp-path'};
    $self->{openslxBasePath}      = $info->{'openslx-base-path'};
    $self->{openslxConfigPath}    = $info->{'openslx-config-path'};
    $self->{attrs}                = $info->{'plugin-attrs'};
    $self->{vendorOsPath}         = $info->{'vendor-os-path'};

    my $files = $self->{attrs}->{'auth::files'};
    my $krb = $self->{attrs}->{'auth::krb'};
    my $krbScript = $self->{attrs}->{'auth::krbscript'};
    my $autoMount = $self->{attrs}->{'auth::automount'};
    my $automntScript = $self->{attrs}->{'auth::automnt_script'};

    if ($krb && !-d $files) {
        print "* KerberOS enabled, but path $files from auth::files not found. Configuration canceld.\n";
        exit 1;
    }
    if ($krb && ! -e "$files/krb5.conf") {
        print "* KerberOS enabled, but needed Configfile $files/krb5.conf not found. Installation stopped.\n";
        exit 1;
    }
    if ($krb && $krbScript ne '' && ! -e "$files/$krbScript") {
        print "* KerberOS enabled, but needed Configfile $files/$krbScript not found. Installation stopped.\n";
        exit 1;
    }
    if ($autoMount && $automntScript ne '' && ! -e "$files/$automntScript") {
        print "* auth::automnt_script $files/$automntScript not found. Installation stopped.\n";
        exit 1;
    }

    # everything worked, now we can copy
    if ( -e $files) {
        system("cp -r $self->{attrs}->{'auth::files'}/* $self->{pluginRepositoryPath}/");
    }
}

sub _setPasswordForUser
{
    my $self = shift;
    my $username = shift;
    my $password = shift;
    
    my $hashedPassword = $self->_hashPassword($password);

    # now read, change and write shadow-file in atomic manner:
    my $shadowFile = '/etc/shadow';
    if (!-e $shadowFile) {
        spitFile( $shadowFile, '');
    }
    slxsystem("cp -r $shadowFile $shadowFile~");
    my $shadowFH;
    open($shadowFH, '+<', $shadowFile)
        or croak _tr("could not open file '%s'! (%s)", $shadowFile, $!);
    # needs fcntl, but noone else should touch it.
    #flock($shadowFH, LOCK_EX)
    #    or croak _tr("could not lock file '%s'! (%s)", $shadowFile, $!);
    my $lastChanged = int(time()/24/60/60);
    my $newEntry 
        = "$username:$hashedPassword:$lastChanged:0:99999:7:::";
    my $content = do { local $/; <$shadowFH> };
    if ($content =~ m{^$username:}ims) {
        $content =~ s{^$username:.*$}{$newEntry}ms;
    } else {
        $content .= "$newEntry\n";
    }
    seek($shadowFH, 0, 0)
       or croak _tr("could not seek file '%s'! (%s)", $shadowFile, $!);
    print $shadowFH $content
        or croak _tr("could not write to file '%s'! (%s)", $shadowFile, $!);
    close($shadowFH)
        or croak _tr("could not close file '%s'! (%s)", $shadowFile, $!);
    unlink "$shadowFile~";

    # create missing entrys in /etc/shadow, else the system is screwed.
    slxsystem("pwconv");
    # Just on suse...   on the other side, -c blowfish is not really needed as argument 
    slxsystem("echo root:$password |chpasswd");
}

sub _hashPassword
{
    my $self = shift;
    my $password = shift;
   
    # -1 = md5
    my $hashedPassword = qx{openssl passwd -1 $password};

    chomp $hashedPassword;

    return $hashedPassword;
}


sub _writeLdapConf
{
    my $self = shift;
    
    my $ldapUri = $self->{attrs}->{'auth::ldapuri'};
    my $ldapBase = $self->{attrs}->{'auth::ldapbase'};
    my $nss_base_passwd = $self->{attrs}->{'auth::nss_base_passwd'};
    my $nss_base_group = $self->{attrs}->{'auth::nss_base_group'};
    my $ldap_options = $self->{attrs}->{'auth::ldap_options'};
    my $ldapBindDn = $self->{attrs}->{'auth::ldapbinddn'};
    my $ldapBindPw = $self->{attrs}->{'auth::ldapbindpw'};

    my $ldapConf = "# ldap.conf created by OpenSLX auth-plugin\n";
    $ldapConf .= "URI $ldapUri\n";
    $ldapConf .= "BASE $ldapBase\n";
    if($nss_base_passwd ne '') {
        $ldapConf .= "nss_base_passwd $nss_base_passwd\n";
    }
    if($nss_base_group ne '') {
        $ldapConf .= "nss_base_group $nss_base_group\n";
    }
    if($ldap_options ne '') {
        # hack. \n is not newline in this string. Repair it.
        $ldap_options =~ s/\\n/\n/g;
        $ldapConf .= "$ldap_options\n";
    }
    if($ldapBindDn ne '') {
        $ldapConf .= "binddn $ldapBindDn\n";
    }
    if($ldapBindDn ne '') {
        $ldapConf .= "bindpw $ldapBindPw\n";
    }
    # if LDAP is damaged, we want to be able to access the system, even if
    # it takes a while to get to the login prompt. Waiting time could be
    # smaller.
    # Another workaround would be "nss_initgroups_ignoreusers"
    $ldapConf .= "nss_reconnect_tries 2\n";
    $ldapConf .= "nss_reconnect_sleeptime 1\n";
    $ldapConf .= "nss_reconnect_maxsleeptime 2\n";
    $ldapConf .= "nss_reconnect_maxconntries 2\n";

    spitFile("$self->{'pluginRepositoryPath'}/ldap.conf.slx", $ldapConf);

    return;
}


sub _writeAutomountConf
{
    my $self = shift;

    my $automntSrc = $self->{attrs}->{'auth::automnt_src'};
    my $automntDir = $self->{attrs}->{'auth::automnt_dir'};
    my $automntScript = $self->{attrs}->{'auth::automnt_script'};
    my $nfs4 = $self->{attrs}->{'auth::nfs4'};
    my $idmapDomain = $self->{attrs}->{'auth::idmap_domain'};
    # automntSrc looks like proto://server/path/foo
    (my $autoProto = $automntSrc ) =~ s,://.*,,;
    (my $autoHost = $automntSrc ) =~ s,.*://,,;
    $autoHost =~ s,/.*,,g;
    (my $autoSrcPath = $automntSrc ) =~ s,.*$autoHost,,;
    my $autoConf;

    if ($automntSrc ne '' && $automntScript ne '') {
        print "* auth::automnt_src and auth::automnt_script enabled. Both won't work together.";
        print "  You need to disable (set the value to '' (empty) one of them!";
        print "  Automount configuration failed.";
        exit 1;
    }

    # nfs
    if ($autoProto eq 'nfs' && ! $nfs4) {
        my $autoConf = "# created by auth-Plugin\n";
        $autoConf .= "*   -fstype=nfs,rsize=32768,wsize=32768,rw  $autoHost:$autoSrcPath/&\n";
        spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf);
    # nfs4
    } elsif ($autoProto =~ m/nfs/ && $nfs4 && $idmapDomain ne '') {
        my $autoConf = "# created by auth-Plugin\n";
        # Path is tricky. NFS4 starts from the exported dir and not from the root dir
        # e.g. in exports: "/export/homdirs 132.230.4.0/255.255.255.0(rw,fsid=0,insecure,no_subtree_check,async)"
        # "mount -t nfs4 server:/$user /home/$user" and not server:/export/homdirs/$user!
        # nfs4 needs fsid=0
        $autoConf .= "*   -fstype=nfs4,fsid=0,rsize=32768,wsize=32768,rw  $autoHost:/&\n";
        spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf);
    # automnt script
    } elsif ( $automntScript ne '') {
        $autoConf .= "# not used, because we use auth::authmnt_script\n";
        spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf);
    } else {
        print "* Automount Configuration failed. Unknown protocol in auth::automnt_src or auth::nfs4 and auth::idmap_domain not enabled.\n";
        exit 1;
    }
   
    
    my $autoMaster;
    if ($autoProto =~ m/nfs/) {
        $autoMaster = "# created by auth-Plugin\n";
        $autoMaster .= "$automntDir        /etc/auto.slx\n";
        spitFile("$self->{'pluginRepositoryPath'}/auto.master", $autoMaster);
    } elsif ($automntScript ne '') {
        #todo: openslx path is hard coded. Maybe we should move this block to stage3. Just how with
        #    without doing all the tests above in Stage3 and being able to configuring client dependend
        #    autmount for each host different
        $autoMaster = "# created by auth-Plugin\n";
        $autoMaster .= "$automntDir        program:/opt/openslx/plugin-repo/auth/$automntScript\n";
        spitFile("$self->{'pluginRepositoryPath'}/auto.master", $autoMaster);
    }

    return;
}



sub _krbConf
{
    my $self = shift;
    # currently not used...
   
}

1;