summaryrefslogtreecommitdiffstats
path: root/perl-install/authentication.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl-install/authentication.pm')
-rw-r--r--perl-install/authentication.pm1020
1 files changed, 883 insertions, 137 deletions
diff --git a/perl-install/authentication.pm b/perl-install/authentication.pm
index c085adc35..55491edfb 100644
--- a/perl-install/authentication.pm
+++ b/perl-install/authentication.pm
@@ -1,125 +1,477 @@
-package authentication; # $Id$
+package authentication;
use common;
-use any;
+my $authentication;
-sub kinds() {
- ('local', 'LDAP', 'NIS', 'winbind');
+sub kinds {
+ my $no_para = @_ == 0;
+ my ($do_pkgs, $_meta_class) = @_;
+ my $allow_SmartCard = $no_para || $do_pkgs->is_available('castella-pam');
+ (
+ 'LDAP',
+ 'KRB5',
+ 'winbind',
+ 'NIS',
+ if_($allow_SmartCard, 'SmartCard'),
+ 'local',
+ );
}
-sub kind2description {
+
+sub kind2name {
my ($kind) = @_;
- ${{ local => N("Local files"), LDAP => N("LDAP"), NIS => N("NIS"), winbind => N("Windows Domain") }}{$kind};
+ # Keep the following strings in sync with kind2description ones!!!
+ ${{ local => N("Local file"),
+ LDAP => N("LDAP"),
+ NIS => N("NIS"),
+ SmartCard => N("Smart Card"),
+ winbind => N("Windows Domain"),
+ KRB5 => N("Kerberos 5") }}{$kind};
}
+
+my %kind2pam_kind = (
+ local => [],
+ SmartCard => ['castella'],
+ LDAP => ['ldap'],
+ NIS => [],
+ KRB5 => ['krb5'],
+ winbind => ['winbind'],
+);
+
+my %kind2nsswitch = (
+ local => [],
+ SmartCard => [],
+ LDAP => ['ldap'],
+ NIS => ['nis'],
+ KRB5 => ['ldap'],
+ winbind => ['winbind'],
+);
+
+my $lib = get_libdir();
+
+my %kind2packages = (
+ local => [],
+ SmartCard => [ 'castella-pam' ],
+ LDAP => [ 'openldap-clients', 'nss-pam-ldapd', 'autofs', 'nss_updatedb' ],
+ KRB5 => [ 'nss-pam-ldapd', 'pam_krb5', "${lib}sasl2-plug-gssapi", 'nss_updatedb' ],
+ NIS => [ 'ypbind', 'autofs' ],
+ winbind => [ 'samba-winbind', 'nss-pam-ldapd', 'pam_krb5', "${lib}sasl2-plug-gssapi" ],
+);
+
+
+sub kind2description_raw {
+ my (@kinds) = @_;
+ my %kind2description = (
+ local => [ N("Local file:"), N("Use local for all authentication and information user tell in local file"), ],
+ LDAP => [ N("LDAP:"), N("Tells your computer to use LDAP for some or all authentication. LDAP consolidates certain types of information within your organization."), ],
+ NIS => [ N("NIS:"), N("Allows you to run a group of computers in the same Network Information Service domain with a common password and group file."), ],
+ winbind => [ N("Windows Domain:"), N("Winbind allows the system to retrieve information and authenticate users in a Windows domain."), ],
+ KRB5 => [ N("Kerberos 5 :"), N("With Kerberos and LDAP for authentication in Active Directory Server "), ],
+ );
+ join('', map { $_ ? qq($_->[0]\n$_->[1]) : '' } map { $kind2description{$_} } @kinds);
+}
+
+sub kind2description {
+ my (@kinds) = @_;
+ join('', map { $_ ? qq($_\n\n) : '' } map { kind2description_raw($_) } @kinds);
+}
+
sub to_kind {
my ($authentication) = @_;
- (find { $authentication->{$_} } kinds()) || 'local';
+ (find { exists $authentication->{$_} } kinds()) || 'local';
+}
+
+sub domain_to_ldap_domain {
+ my ($domain) = @_;
+ join(',', map { "dc=$_" } split /\./, $domain);
}
sub ask_parameters {
- my ($in, $netc, $authentication, $kind) = @_;
+ my ($in, $net, $authentication, $kind) = @_;
- my $val = $authentication->{$kind};
+ #- keep only this authentication kind
+ foreach (kinds()) {
+ delete $authentication->{$_} if $_ ne $kind;
+ }
+ # do not enable ccreds unless required
+ undef $authentication->{ccreds};
if ($kind eq 'LDAP') {
- $val ||= 'ldap.' . $netc->{DOMAINNAME};
- $netc->{LDAPDOMAIN} ||= join(',', map { "dc=$_" } split /\./, $netc->{DOMAINNAME});
- $in->ask_from('',
- N("Authentication LDAP"),
- [ { label => N("LDAP Base dn"), val => \$netc->{LDAPDOMAIN} },
- { label => N("LDAP Server"), val => \$val },
+ $authentication->{LDAPDOMAIN} ||= domain_to_ldap_domain($net->{resolv}{DOMAINNAME});
+ $authentication->{ccreds} = 1;
+
+ # this package must be installed for 'Fetch DN' button to actually work
+ $in->do_pkgs->ensure_are_installed([ 'openldap-clients' ], 1) or return;
+
+ $in->ask_from('', N(" "),
+ [ { label => N("Welcome to the Authentication Wizard"), title => 1 },
+ {},
+ { label => N("You have selected LDAP authentication. Please review the configuration options below "), },
+ {},
+ { label => N("LDAP Server"), val => \$authentication->{LDAP_server} },
+ { label => N("Base dn"), val => \$authentication->{LDAPDOMAIN} },
+ { val => N("Fetch base Dn "), type => 'button' , clicked_may_quit => sub { $authentication->{LDAPDOMAIN} = fetch_dn($authentication->{LDAP_server}); 0 } },
+ {},
+ { text => N("Use encrypt connection with TLS "), val => \$authentication->{cafile}, type => 'bool' },
+ { val => N("Download CA Certificate "), type => 'button' , disabled => sub { !$authentication->{cafile} }, clicked_may_quit => sub { $authentication->{file} = add_cafile(); 0 } },
+
+ { text => N("Use Disconnect mode "), val => \$authentication->{ccreds}, type => 'bool' },
+ { text => N("Use anonymous BIND "), val => \$authentication->{anonymous}, type => 'bool' , advanced => 1 },
+ { text => N(" "), advanced => 1 },
+ { label => N("Bind DN "), val => \$authentication->{LDAP_binddn}, disabled => sub { !$authentication->{anonymous} }, advanced => 1 },
+ { label => N("Bind Password "), val => \$authentication->{LDAP_bindpwd}, disabled => sub { !$authentication->{anonymous} }, advanced => 1 },
+ { text => N(" "), advanced => 1 },
+ { text => N("Advanced path for group "), val => \$authentication->{nssgrp}, type => 'bool' , advanced => 1 },
+ { text => N(" "), advanced => 1 },
+ { label => N("Password base"), val => \$authentication->{nss_pwd}, disabled => sub { !$authentication->{nssgrp} }, advanced => 1 },
+ { label => N("Group base"), val => \$authentication->{nss_grp}, disabled => sub { !$authentication->{nssgrp} }, advanced => 1 },
+ { label => N("Shadow base"), val => \$authentication->{nss_shadow}, disabled => sub { !$authentication->{nssgrp} }, advanced => 1 },
+ { text => N(" "), advanced => 1 },
+ ]) or return;
+ } elsif ($kind eq 'KRB5') {
+
+ $authentication->{AD_domain} ||= $net->{resolv}{DOMAINNAME};
+ $in->do_pkgs->ensure_are_installed([ 'perl-Net-DNS' ], 1) or return;
+ my @srvs = query_srv_names($authentication->{AD_domain}); #FIXME: update this list if the REALM has changed
+ $authentication->{AD_server} ||= $srvs[0] if @srvs;
+ my $AD_user = $authentication->{AD_user} =~ /(.*)\@\Q$authentication->{AD_domain}\E$/ ? $1 : $authentication->{AD_user};
+ $authentication->{ccreds} = 1;
+
+ $in->ask_from('', N(" "),
+ [ { label => N("Welcome to the Authentication Wizard"), title => 1 },
+ {},
+ { label => N("You have selected Kerberos 5 authentication. Please review the configuration options below "), },
+ {},
+ { label => N("Realm "), val => \$authentication->{AD_domain} },
+ {},
+ { label => N("KDCs Servers"), title => 1, val => \$authentication->{AD_server} , list => \@srvs , not_edit => 0, title => 1 },
+ {},
+ { text => N("Use DNS to locate KDC for the realm"), val => \$authentication->{KRB_host_lookup}, type => 'bool' },
+ { text => N("Use DNS to locate realms"), val => \$authentication->{KRB_dns_lookup}, type => 'bool' },
+ { text => N("Use Disconnect mode "), val => \$authentication->{ccreds}, type => 'bool' },
]) or return;
- } elsif ($kind eq 'NIS') {
- $val ||= 'broadcast';
- $in->ask_from('',
- N("Authentication NIS"),
- [ { label => N("NIS Domain"), val => \ ($netc->{NISDOMAIN} ||= $netc->{DOMAINNAME}) },
- { label => N("NIS Server"), val => \$val, list => ["broadcast"], not_edit => 0 },
+
+my %level = (
+ 1 => N("Use local file for users information"),
+ 2 => N("Use LDAP for users information"),
+ );
+
+ $in->ask_from('', N(" "),
+ [ { label => N(" "), title => 1 },
+ {},
+ { label => N("You have selected Kerberos 5 for authentication, now you must choose the type of users information "), },
+ {},
+ { label => "" , val => \$authentication->{nsskrb}, type => 'list', list => [ keys %level ], format => sub { $level{$_[0]} } },
+ {},
+ { label => N("LDAP Server"), val => \$authentication->{LDAP_server}, disabled => sub { $authentication->{nsskrb} eq "1" } },
+ { label => N("Base dn"), val => \$authentication->{LDAPDOMAIN} , disabled => sub { $authentication->{nsskrb} eq "1" } },
+ { val => N("Fetch base DN "), type => 'button' , clicked_may_quit => sub { $authentication->{LDAPDOMAIN} = fetch_dn($authentication->{LDAP_server}); 0 }, disabled => sub { $authentication->{nsskrb} eq "1" } },
+ {},
+ { text => N("Use encrypt connection with TLS "), val => \$authentication->{cafile}, type => 'bool',, disabled => sub { $authentication->{nsskrb} eq "1" } },
+ { val => N("Download CA Certificate "), type => 'button' , disabled => sub { !$authentication->{cafile} }, clicked_may_quit => sub { $authentication->{file} = add_cafile(); 0 } },
+ { text => N("Use anonymous BIND "), val => \$authentication->{anonymous}, type => 'bool', disabled => sub { $authentication->{nsskrb} eq "1" } },
+ { label => N("Bind DN "), val => \$authentication->{LDAP_binddn}, disabled => sub { !$authentication->{anonymous} } },
+ { label => N("Bind Password "), val => \$authentication->{LDAP_bindpwd}, disabled => sub { !$authentication->{anonymous} } },
+ {},
+ ]) or return;
+
+ $authentication->{AD_user} = !$AD_user || $authentication->{sub_kind} eq 'anonymous' ? '' :
+ $AD_user =~ /@/ ? $AD_user : "$AD_user\@$authentication->{AD_domain}";
+ $authentication->{AD_password} = '' if !$authentication->{AD_user};
+
+
+ } elsif ($kind eq 'NIS') {
+ $authentication->{NIS_server} ||= 'broadcast';
+ $net->{network}{NISDOMAIN} ||= $net->{resolv}{DOMAINNAME};
+ $in->ask_from('', N(" "),
+ [ { label => N("Welcome to the Authentication Wizard"), title => 1 },
+ {},
+ { label => N("You have selected NIS authentication. Please review the configuration options below "), },
+ {},
+ { label => N("NIS Domain"), val => \$net->{network}{NISDOMAIN} },
+ { label => N("NIS Server"), val => \$authentication->{NIS_server}, list => ["broadcast"], not_edit => 0 },
+ {},
]) or return;
} elsif ($kind eq 'winbind') {
#- maybe we should browse the network like diskdrake --smb and get the 'doze server names in a list
- #- but networking isn't setup yet necessarily
- $in->ask_warn('', N("For this to work for a W2K PDC, you will probably need to have the admin run: C:\\>net localgroup \"Pre-Windows 2000 Compatible Access\" everyone /add and reboot the server.\nYou will also need the username/password of a Domain Admin to join the machine to the Windows(TM) domain.\nIf networking is not yet enabled, Drakx will attempt to join the domain after the network setup step.\nShould this setup fail for some reason and domain authentication is not working, run 'smbpasswd -j DOMAIN -U USER%%PASSWORD' using your Windows(tm) Domain, and Admin Username/Password, after system boot.\nThe command 'wbinfo -t' will test whether your authentication secrets are good."));
- $in->ask_from('',
- N("Authentication Windows Domain"),
- [ { label => N("Windows Domain"), val => \ ($netc->{WINDOMAIN} ||= $netc->{DOMAINNAME}) },
- { label => N("Domain Admin User Name"), val => \$val },
- { label => N("Domain Admin Password"), val => \$authentication->{winpass}, hidden => 1 },
+ #- but networking is not setup yet necessarily
+ #
+ my @sec_domain = (
+ "Windows Active Directory Domain",
+ "Windows NT4 Domain",
+);
+
+
+ $authentication->{DNS_domain} ||= $net->{resolv}{DOMAINNAME};
+ $authentication->{WINDOMAIN} ||= $net->{resolv}{DOMAINNAME};
+ $in->do_pkgs->ensure_are_installed([ 'samba-client' ], 1) or return;
+ my @domains=list_domains();
+
+ $in->ask_from('', N(" "),
+ [ { label => N("Welcome to the Authentication Wizard"), title => 1 },
+ {},
+ { label => N("You have selected Windows Domain authentication. Please review the configuration options below "), },
+ {},
+ { label => N("Windows Domain"), val => \$authentication->{WINDOMAIN}, list => \@domains, not_edit => 1 },
+ {},
+ { label => N("Domain Model "), val => \$authentication->{model}, list => \@sec_domain , not_edit => 1 },
+ {},
+ { label => N("Active Directory Realm "), val => \$authentication->{AD_domain} , disabled => sub { $authentication->{model} eq "Windows NT4 Domain" } },
+ { label => N("DNS Domain"), val => \$authentication->{DNS_domain} , disabled => sub { $authentication->{model} eq "Windows NT4 Domain" } },
+ { label => N("DC Server"), val => \$authentication->{AD_server} , disabled => sub { $authentication->{model} eq "Windows NT4 Domain" } },
+ {},
]) or return;
}
- #- keep only one authentication
- delete $authentication->{$_} foreach kinds();
- $authentication->{$kind} = $val;
+ $authentication->{$kind} ||= 1;
+ 1;
+}
+sub ask_root_password_and_authentication {
+ my ($in, $net, $superuser, $authentication, $meta_class, $security) = @_;
+
+ my $kind = to_kind($authentication);
+ my @kinds = kinds($in->do_pkgs, $meta_class);
+
+ $in->ask_from_({
+ title => N("Authentication"),
+ messages => N("Set administrator (root) password"),
+ advanced_label => N("Authentication method"),
+ advanced_messages => kind2description(@kinds),
+ interactive_help_id => "setRootPassword",
+ cancel => ($security <= 2 ?
+ #-PO: keep this short or else the buttons will not fit in the window
+ N("No password") : ''),
+ focus_first => 1,
+ callbacks => {
+ complete => sub {
+ check_given_password($in, $superuser, 2 * $security) or return 1,0;
+ return 0;
+ } } }, [
+{ label => N("Password"), val => \$superuser->{password}, hidden => 1 },
+{ label => N("Password (again)"), val => \$superuser->{password2}, hidden => 1 },
+{ label => N("Authentication"), val => \$kind, type => 'list', list => \@kinds, format => \&kind2name, advanced => 1 },
+ ]) or delete $superuser->{password};
+
+ ask_parameters($in, $net, $authentication, $kind) or goto &ask_root_password_and_authentication;
+}
+
+sub check_given_password {
+ my ($in, $u, $min_length) = @_;
+ if ($u->{password} ne $u->{password2}) {
+ $in->ask_warn('', [ N("The passwords do not match"), N("Please try again") ]);
+ 0;
+ } elsif (length $u->{password} < $min_length) {
+ $in->ask_warn('', N("This password is too short (it must be at least %d characters long)", $min_length));
+ 0;
+ } else {
+ 1;
+ }
+}
+
+sub get() {
+ my $system_auth = cat_("/etc/pam.d/system-auth");
+ my $authentication = {
+ blowfish => to_bool($system_auth =~ /\$2a\$/),
+ md5 => to_bool($system_auth =~ /md5/),
+ sha256 => to_bool($system_auth =~ /sha256/),
+ sha512 => to_bool($system_auth =~ /sha512/),
+ shadow => to_bool($system_auth =~ /shadow/),
+ };
+
+ my @pam_kinds = get_pam_authentication_kinds();
+ if (my $kind = find { intersection(\@pam_kinds, $kind2pam_kind{$_}) } keys %kind2pam_kind) {
+ $authentication->{$kind} = '';
+ } else {
+ #- we can't use pam to detect NIS
+ if (my $yp_conf = read_yp_conf()) {
+ $authentication->{NIS} = 1;
+ map_each { $authentication->{"NIS_$::a"} = $::b } %$yp_conf;
+ }
+ }
+ $authentication;
+}
+
+sub install_needed_packages {
+ my ($do_pkgs, $kind, $ccreds) = @_;
+ if (my $pkgs = $kind2packages{$kind}) {
+ # install ccreds if required
+ $ccreds and push(@$pkgs, 'pam_ccreds');
+ #- automatic during install
+ $do_pkgs->ensure_are_installed($pkgs, $::isInstall) or return;
+ } else {
+ log::l("ERROR: $kind not listed in kind2packages");
+ }
1;
}
sub set {
- my ($in, $netc, $authentication, $when_network_is_up) = @_;
+ my ($in, $net, $authentication, $o_when_network_is_up) = @_;
+
+ install_needed_packages($in->do_pkgs, to_kind($authentication), $authentication->{ccreds}) or return;
+ set_raw($net, $authentication, $o_when_network_is_up);
- any::enableShadow() if $authentication->{shadow};
+ require services;
+ services::set_status('network-auth', to_kind($authentication) ne 'local', 'dont_apply');
+}
- my $kind = authentication::to_kind($authentication);
- my $val = $authentication->{$kind};
+sub set_raw {
+ my ($net, $authentication, $o_when_network_is_up) = @_;
- log::l("authentication::set $kind with $val");
+ my $conf_file = "$::prefix/etc/sysconfig/drakauth";
+ my $when_network_is_up = $o_when_network_is_up || sub { my ($f) = @_; $f->() };
- if ($kind eq 'LDAP') {
+ enable_shadow() if $authentication->{shadow};
+
+ my $kind = to_kind($authentication);
+
+ log::l("authentication::set $kind");
+
+ my $pam_modules = $kind2pam_kind{$kind} or log::l("kind2pam_kind does not know $kind");
+ $pam_modules ||= [];
+ set_pam_authentication($pam_modules, $authentication->{ccreds});
+
+ my $nsswitch = $kind2nsswitch{$kind} or log::l("kind2nsswitch does not know $kind");
+ $nsswitch ||= [];
+ set_nsswitch_priority($nsswitch, $authentication->{ccreds});
+
+ if ($kind eq 'local') {
- my $domain = $netc->{LDAPDOMAIN} || do {
- my $s = run_program::rooted_get_stdout($::prefix, 'ldapsearch', '-x', '-h', $val, '-b', '', '-s', 'base', '+');
- first($s =~ /namingContexts: (.+)/);
- } or log::l("no ldap domain found on server $val"), return;
+ output($conf_file, <<EOF);
+auth=Local File
+server=none
+realm=none
+EOF
- $in->do_pkgs->install(qw(openldap-clients nss_ldap pam_ldap autofs));
- set_nsswitch_priority('ldap');
- set_pam_authentication('ldap');
- set_ldap_conf($domain, $val, 1);
+
+ } elsif ($kind eq 'SmartCard') {
+ } elsif ($kind eq 'LDAP') {
+
+ configure_nss_ldap($authentication);
+
+ output($conf_file, <<EOF);
+auth=LDAP Directory
+server=$authentication->{LDAP_server}
+realm=$authentication->{LDAPDOMAIN}
+EOF
+
+ if ($authentication->{ccreds}) {
+ run_program::rooted($::prefix, '/usr/sbin/nss_updatedb.cron'); # updates offline cache.
+ }
+
+ } elsif ($kind eq 'KRB5') {
+
+ configure_krb5_for_AD($authentication);
+ configure_nss_ldap($authentication);
+
+ output($conf_file, <<EOF);
+auth=Kerberos 5
+server=$authentication->{AD_server}
+realm=$authentication->{AD_domain}
+EOF
+
} elsif ($kind eq 'NIS') {
- $in->do_pkgs->install('ypbind');
- my $domain = $netc->{NISDOMAIN};
- $domain || $val ne "broadcast" or die \N("Can't use broadcast with no NIS domain");
- my $t = $domain ? "domain $domain" . ($val ne "broadcast" && " server") : "ypserver";
+ my $domain = $net->{network}{NISDOMAIN};
+ my $NIS_server = $authentication->{NIS_server};
+ $domain || $NIS_server ne "broadcast" or die N("Cannot use broadcast with no NIS domain");
+ my $t = $domain ?
+ ($NIS_server eq 'broadcast' ?
+ "domain $domain broadcast" :
+ "domain $domain server $NIS_server") :
+ "server $NIS_server";
+
substInFile {
- $_ = "#~$_" unless /^#/;
- $_ .= "$t $val\n" if eof;
+ if (/^#/) {
+ $_ = '' if /^#\Q[PREVIOUS]/;
+ } else {
+ $_ = "#[PREVIOUS] $_";
+ }
+ $_ .= "$t\n" if eof;
} "$::prefix/etc/yp.conf";
- set_nsswitch_priority('nis');
#- no need to modify system-auth for nis
$when_network_is_up->(sub {
run_program::rooted($::prefix, 'nisdomainname', $domain);
run_program::rooted($::prefix, 'service', 'ypbind', 'restart');
- }) if !$::isInstall; #- TODO: also do it during install since nis can be useful to resolve domain names. Not done because 9.2-RC
+ });
+
+ output($conf_file, <<EOF);
+auth=$kind
+server=$NIS_server
+realm=$domain
+EOF
+
} elsif ($kind eq 'winbind') {
- my $domain = $netc->{WINDOMAIN};
- $domain =~ tr/a-z/A-Z/;
- $in->do_pkgs->install(qw(samba-winbind samba-common));
- set_pam_authentication('winbind');
+ my $domain = uc $authentication->{WINDOMAIN};
+ ($authentication->{winuser}, $authentication->{winpass}) = auth();
- require network::smb;
- network::smb::write_smb_conf($domain);
+ if ($authentication->{model} eq "Windows NT4 Domain") {
+
+ require fs::remote::smb;
+ fs::remote::smb::write_smb_conf($domain);
run_program::rooted($::prefix, "chkconfig", "--level", "35", "winbind", "on");
mkdir_p("$::prefix/home/$domain");
+ run_program::rooted($::prefix, 'service', 'smb', 'restart');
+ run_program::rooted($::prefix, 'service', 'winbind', 'restart');
#- defer running smbpassword until the network is up
+
$when_network_is_up->(sub {
- run_program::rooted($::prefix, 'smbpasswd', '-j', $domain, '-U', $val . '%' . $authentication->{winpass});
+ run_program::raw({ root => $::prefix, sensitive_arguments => 1 },
+ #'net', 'join', $domain, '-U', $authentication->{winuser} . '%' . $authentication->{winpass});
+ 'echo', '"', 'net', 'join', $domain, '-U', $authentication->{winuser} . '%' . $authentication->{winpass}, '"');
});
- }
+
+ output($conf_file, <<EOF);
+auth=Windows NT4 Domain
+server= none
+realm=$domain
+EOF
+
+
+
+
+ } else {
+ # FIXME: the DC isn't named ads.domain... try to do reserve lookup?
+ $authentication->{AD_server} ||= 'ads.' . $authentication->{AD_domain};
+ my $domain = uc $authentication->{WINDOMAIN};
+ my $realm = $authentication->{AD_domain};
+ ($authentication->{winuser}, $authentication->{winpass}) = auth();
+ configure_krb5_for_AD($authentication);
+
+ require fs::remote::smb;
+ fs::remote::smb::write_smb_ads_conf($domain,$realm);
+ run_program::rooted($::prefix, "chkconfig", "--level", "35", "winbind", "on");
+ mkdir_p("$::prefix/home/$domain");
+ run_program::rooted($::prefix, 'net', 'time', 'set', '-S', $authentication->{AD_server});
+ run_program::rooted($::prefix, 'service', 'smb', 'restart');
+
+ $when_network_is_up->(sub {
+ run_program::raw({ root => $::prefix, sensitive_arguments => 1 },
+ 'net', 'ads', 'join', '-U', $authentication->{winuser} . '%' . $authentication->{winpass});
+ run_program::rooted($::prefix, 'service', 'winbind', 'restart');
+ });
+
+ #FIXME: perhaps save the defaults values ?
+ output($conf_file, <<EOF);
+auth=Windows Active Directory Domain
+server= none
+realm=$realm
+EOF
+ } }
+ 1;
}
sub pam_modules() {
- 'pam_ldap', 'pam_winbind', 'pam_mkhomedir';
+ 'pam_ldap', 'pam_castella', 'pam_winbind', 'pam_krb5', 'pam_mkhomedir', 'pam_ccreds', 'pam_deny' , 'pam_permit';
}
sub pam_module_from_path {
- $_[0] && $_[0] =~ m|/lib/security/(pam_.*)\.so| && $1;
+ $_[0] && $_[0] =~ m|(/lib/security/)?(pam_.*)\.so| && $2;
}
sub pam_module_to_path {
- "/lib/security/$_[0].so";
+ "$_[0].so";
}
sub pam_format_line {
my ($type, $control, $module, @para) = @_;
@@ -129,109 +481,503 @@ sub pam_format_line {
sub get_raw_pam_authentication() {
my %before_deny;
foreach (cat_("$::prefix/etc/pam.d/system-auth")) {
- my ($type, $control, $module, @para) = split;
+ my ($type, $_control, $other) = /(\S+)\s+(\[.*?\]|\S+)\s+(.*)/;
+ my ($module, @para) = split(' ', $other);
if ($module = pam_module_from_path($module)) {
- $before_deny{$type}{$module} = \@para if $control eq 'sufficient' && member($module, pam_modules());
+ $before_deny{$type}{$module} = \@para if member($module, pam_modules());
}
}
\%before_deny;
}
-sub set_raw_pam_authentication {
- my ($before_deny, $before_first) = @_;
+sub get_pam_authentication_kinds() {
+ my $before_deny = get_raw_pam_authentication();
+ map { s/pam_//; $_ } keys %{$before_deny->{auth}};
+}
+
+sub sufficient {
+ my ($ccreds, $module, $type) = @_;
+
+ $ccreds && member($module, 'pam_tcb' , 'pam_winbind') ?
+ 'sufficient' :
+ $ccreds && member($module, 'pam_ldap', 'pam_krb5') && $type eq 'account' ?
+ '[authinfo_unavail=ignore default=done]' :
+ $ccreds && member($module, 'pam_ldap', 'pam_krb5') && $type eq 'password' ?
+ 'sufficient' :
+ $ccreds && member($module, 'pam_ldap', 'pam_krb5') ?
+ '[authinfo_unavail=ignore user_unknown=ignore success=1 default=2]' :
+ 'sufficient';
+}
+
+sub pam_sufficient_line {
+ my ($ccreds, $type, $module, @para) = @_;
+ my $control = sufficient($ccreds, $module, $type);
+ if ($module eq 'pam_winbind') {
+ push @para, 'cached_login';
+ }
+ pam_format_line($type, $control, $module, @para);
+}
+
+
+
+
+
+
+sub set_pam_authentication {
+ my ($authentication_kinds, $o_ccreds) = @_;
+
+ my %special = (
+ auth => [ difference2($authentication_kinds,, [ 'mount' ]) ],
+ account => [ difference2($authentication_kinds, [ 'castella', 'mount', 'ccreds' ]) ],
+ password => [ intersection($authentication_kinds, [ 'ldap', 'krb5', 'ccreds' ]) ],
+ );
+ my %before_first = (
+ auth => member('mount', @$authentication_kinds) ? pam_format_line('auth', 'required', 'pam_mount') : '',
+ session =>
+ intersection($authentication_kinds, [ 'winbind', 'krb5', 'ldap' ])
+ ? pam_format_line('session', 'optional', 'pam_mkhomedir', 'skel=/etc/skel/', 'umask=0022') :
+ member('castella', @$authentication_kinds)
+ ? pam_format_line('session', 'optional', 'pam_castella') : '',
+ );
+ my %after_deny = (
+ session =>
+ member('krb5', @$authentication_kinds)
+ ? pam_format_line('session', 'optional', 'pam_krb5') :
+ member('mount', @$authentication_kinds)
+ ? pam_format_line('session', 'optional', 'pam_mount') : '',
+ );
+
substInFile {
- my ($type, $control, $module, @para) = split;
- my $added_pre_line = '';
+ my ($type, $control, $other) = /(\S+)\s+(\[.*?\]|\S+)\s+(.*)/;
+ my ($module, @para) = split(' ', $other);
if ($module = pam_module_from_path($module)) {
- if ($module eq 'pam_unix' && member($type, 'auth', 'account')) {
- #- ensure use_first_pass option is there
- $_ = pam_format_line($type, 'sufficient', $module, uniq(@para, 'use_first_pass'));
- if ($control eq 'required') {
- #- ensure a pam_deny line is there
- ($control, $module, @para) = ('required', 'pam_deny');
- ($added_pre_line, $_) = ($_, pam_format_line($type, $control, $module));
- }
- }
if (member($module, pam_modules())) {
#- first removing previous config
- warn "dropping line $_";
$_ = '';
- } else {
- if ($before_first->{$type}) {
- foreach my $module (keys %{$before_first->{$type}}) {
- $_ = pam_format_line($type, 'required', $module, @{$before_first->{$type}{$module}}) . $_;
- }
- delete $before_first->{$type};
- }
- if ($control eq 'required' && $module eq 'pam_deny') {
- if ($before_deny->{$type}) {
- foreach my $module (keys %{$before_deny->{$type}}) {
- $_ = pam_format_line($type, 'sufficient', $module, @{$before_deny->{$type}{$module}}) . $_;
- }
- }
+ }
+ if ($module eq 'pam_tcb' && $special{$type}) {
+ my @para_for_last =
+ member($type, 'auth', 'account') ? qw(use_first_pass) : @{[]};
+ @para = difference2(\@para, \@para_for_last);
+
+ my ($before_noask, $ask) = partition { $_ eq 'castella' } @{$special{$type}};
+
+ if (!@$ask) {
+ @para_for_last = grep { $_ ne 'use_first_pass' } @para_for_last;
+ }
+
+ my @l = ((map { [ "pam_$_" ] } @$before_noask),
+ [ 'pam_tcb', @para ],
+ (map { [ "pam_$_" ] } @$ask),
+ );
+ push @{$l[-1]}, @para_for_last;
+
+ $_ = join('', map { pam_sufficient_line($o_ccreds, $type, @$_) } @l);
+
+ if ($control eq 'required') {
+ #- ensure a pam_deny line is there. it will be added below
+ ($module, @para) = ('pam_deny');
+ }
+
+ if ($type eq 'auth' && $o_ccreds) {
+ $_ .= pam_format_line('auth', '[default=done]', 'pam_ccreds', 'action=validate use_first_pass');
+ $_ .= pam_format_line('auth', '[default=done]', 'pam_ccreds', 'action=store');
+ $_ .= pam_format_line('auth', '[default=bad]', 'pam_ccreds', 'action=update');
}
}
- $_ = $added_pre_line . $_;
- }
- } "$::prefix/etc/pam.d/system-auth";
-}
-sub get_pam_authentication_kinds() {
- my $before_deny = get_raw_pam_authentication();
- map { s/pam_//; $_ } keys %{$before_deny->{auth}};
-}
-sub set_pam_authentication {
- my (@authentication_kinds) = @_;
- my $before_deny = {};
- my $before_first = {};
- foreach (@authentication_kinds) {
- my $module = 'pam_' . $_;
- $before_deny->{auth}{$module} = [];
- $before_deny->{account}{$module} = [];
- $before_deny->{password}{$module} = [] if $_ eq 'ldap';
- $before_first->{session}{pam_mkhomedir} = [ 'skel=/etc/skel/', 'umask=0022' ] if $_ eq 'winbind';
- }
- set_raw_pam_authentication($before_deny, $before_first);
+ if (member($module, 'pam_deny', 'pam_permit')) {
+ $_ .= pam_format_line($type, $control,
+ $type eq 'account' && $o_ccreds ? 'pam_permit' : 'pam_deny');
+ }
+ if (my $s = delete $before_first{$type}) {
+ $_ = $s . $_;
+ }
+ if ($control eq 'required' && member($module, 'pam_deny', 'pam_permit', 'pam_tcb')) {
+ if (my $s = delete $after_deny{$type}) {
+ $_ .= $s;
+ }
+ }
+ }
+ } "$::prefix/etc/pam.d/system-auth";
}
sub set_nsswitch_priority {
- my (@kinds) = @_;
- # allowed: files nis ldap dns
+ my ($kinds, $connected) = @_;
+ my @known = qw(nis ldap winbind compat);
substInFile {
if (my ($database, $l) = /^(\s*(?:passwd|shadow|group|automount):\s*)(.*)/) {
- $_ = $database . join(' ', uniq('files', @kinds, split(' ', $l))) . "\n";
- }
+ my @l = difference2([ split(' ', $l) ], \@known);
+ $_ = $database . join(' ', uniq('files', @$kinds, @l)) . "\n";
+ }
+ if (/^\s*(?:passwd|group):/) {
+ my $option = '[NOTFOUND=return] db';
+ if ($connected) {
+ s/$/ $option/ if !/\Q$option/;
+ } else {
+ s/\s*\Q$option//;
+ }
+}
+
} "$::prefix/etc/nsswitch.conf";
}
+sub read_yp_conf() {
+ my $yp_conf = cat_("$::prefix/etc/yp.conf");
+
+ if ($yp_conf =~ /^domain\s+(\S+)\s+(\S+)\s*(.*)/m) {
+ { domain => $1, server => $2 eq 'broadcast' ? 'broadcast' : $3 };
+ } elsif ($yp_conf =~ /^server\s+(.*)/m) {
+ { server => $1 };
+ } else {
+ undef;
+ }
+}
+
+my $special_ldap_cmds = join('|', 'nss_map_attribute', 'nss_map_objectclass');
+sub _after_read_ldap_line {
+ my ($s) = @_;
+ $s =~ s/\b($special_ldap_cmds)\s*/$1 . '_'/e;
+ $s;
+}
+sub _pre_write_ldap_line {
+ my ($s) = @_;
+ $s =~ s/\b($special_ldap_cmds)_/$1 . ' '/e;
+ $s;
+}
+
sub read_ldap_conf() {
- my %conf = map { s/^\s*#.*//; if_(/(\S+)\s+(.*)/, $1 => $2) } cat_("$::prefix/etc/ldap.conf");
+ my %conf = map {
+ s/^\s*#.*//;
+ if_(_after_read_ldap_line($_) =~ /(\S+)\s+(.*)/, $1 => $2);
+ } cat_("$::prefix/etc/nslcd.conf");
\%conf;
}
-sub set_ldap_conf {
- my ($domain, $servers, $b_ssl) = @_;
-
- my %wanted_conf = (
- host => $servers,
- base => $domain,
- port => $b_ssl ? 636 : 389,
- ssl => $b_ssl ? 'on' : 'off',
- nss_base_shadow => "ou=People,$domain",
- nss_base_passwd => "ou=People,$domain",
- nss_base_group => "ou=Group,$domain",
- );
+sub update_ldap_conf {
+ my (%conf) = @_;
substInFile {
- my ($cmd) = /^#?\s*(\w+)\s/;
- if ($cmd && exists $wanted_conf{$cmd}) {
- my $val = $wanted_conf{$cmd};
- $wanted_conf{$cmd} = '';
- $_ = $val ? "$cmd $val\n" : '';
+ my ($cmd) = _after_read_ldap_line($_) =~ /^\s*#?\s*(\w+)\s/;
+ if ($cmd && exists $conf{$cmd}) {
+ my $val = $conf{$cmd};
+ $conf{$cmd} = '';
+ $_ = $val ? _pre_write_ldap_line("$cmd $val\n") : /^\s*#/ ? $_ : "#$_";
}
- } "$::prefix/etc/ldap.conf";
+ if (eof) {
+ foreach my $cmd (keys %conf) {
+ my $val = $conf{$cmd} or next;
+ $_ .= _pre_write_ldap_line("$cmd $val\n");
+ }
+ }
+ } "$::prefix/etc/nslcd.conf";
}
-1;
+sub configure_krb5_for_AD {
+ my ($authentication) = @_;
+
+ my $uc_domain = uc $authentication->{AD_domain};
+ my $krb5_conf_file = "$::prefix/etc/krb5.conf";
+
+ krb5_conf_update($krb5_conf_file,
+ libdefaults => (
+ default_realm => $uc_domain,
+ dns_lookup_realm => $authentication->{KRB_dns_lookup} ? 'true' : 'false',
+ dns_lookup_kdc => $authentication->{KRB_host_lookup} ? 'true' : 'false',
+ default_tgs_enctypes => undef,
+ default_tkt_enctypes => undef,
+ permitted_enctypes => undef,
+ ));
+
+ my @sections = (
+ realms => <<EOF,
+ $uc_domain = {
+ kdc = $authentication->{AD_server}:88
+ admin_server = $authentication->{AD_server}:749
+ default_domain = $authentication->{AD_domain}
+ }
+EOF
+ domain_realm => <<EOF,
+ .$authentication->{AD_domain} = $uc_domain
+ $authentication->{AD_domain} = $uc_domain
+EOF
+ kdc => <<'EOF',
+ profile = /etc/kerberos/krb5kdc/kdc.conf
+EOF
+ pam => <<'EOF',
+ debug = false
+ ticket_lifetime = 36000
+ renew_lifetime = 36000
+ forwardable = true
+ krb4_convert = false
+EOF
+ login => <<'EOF',
+ krb4_convert = false
+ krb4_get_tickets = false
+EOF
+ );
+ foreach (group_by2(@sections)) {
+ my ($section, $txt) = @$_;
+ krb5_conf_overwrite_category($krb5_conf_file, $section => $authentication->{AD_server} ? $txt : '');
+ }
+}
+
+sub krb5_conf_overwrite_category {
+ my ($file, $category, $new_val) = @_;
+
+ my $done;
+ substInFile {
+ if (my $i = /^\s*\[\Q$category\E\]/i ... /^\[/) {
+ if ($new_val) {
+ if ($i == 1) {
+ $_ .= $new_val;
+ $done = 1;
+ } elsif ($i =~ /E/) {
+ $_ = "\n$_";
+ } else {
+ $_ = '';
+ }
+ } else {
+ $_ = '' if $i !~ /E/;
+ }
+ }
+ #- if category has not been found above.
+ if (eof && $new_val && !$done) {
+ $_ .= "\n[$category]\n$new_val";
+ }
+ } $file;
+}
+
+#- same as update_gnomekderc(), but allow spaces around "="
+sub krb5_conf_update {
+ my ($file, $category, %subst_) = @_;
+
+ my %subst = map { lc($_) => [ $_, $subst_{$_} ] } keys %subst_;
+
+ my $s;
+ foreach (MDK::Common::File::cat_($file), "[NOCATEGORY]\n") {
+ if (my $i = /^\s*\[\Q$category\E\]/i ... /^\[/) {
+ if ($i =~ /E/) { #- for last line of category
+ chomp $s; $s .= "\n";
+ $s .= " $_->[0] = $_->[1]\n" foreach grep { defined($_->[1]) } values %subst;
+ %subst = ();
+ } elsif (/^\s*([^=]*?)\s*=/) {
+ if (my $e = delete $subst{lc($1)}) {
+ $_ = defined($e->[1]) ? " $1 = $e->[1]\n" : '';
+ }
+ }
+ }
+ $s .= $_ if !/^\Q[NOCATEGORY]/;
+ }
+
+ #- if category has not been found above.
+ if (keys %subst) {
+ chomp $s;
+ $s .= "\n[$category]\n";
+ $s .= " $_->[0] = $_->[1]\n" foreach grep { defined($_->[1]) } values %subst;
+ }
+
+ MDK::Common::File::output($file, $s);
+
+}
+
+sub query_srv_names {
+ my ($domain) = @_;
+
+ eval { require Net::DNS; 1 } or return;
+ my $res = Net::DNS::Resolver->new;
+ my $query = $res->query("_ldap._tcp.$domain", 'srv') or return;
+ map { $_->target } $query->answer;
+}
+
+sub enable_shadow() {
+ run_program::rooted($::prefix, "pwconv") or log::l("pwconv failed");
+ run_program::rooted($::prefix, "grpconv") or log::l("grpconv failed");
+}
+
+sub salt {
+ my ($nb) = @_;
+ require devices;
+ open(my $F, "/dev/urandom") or die "missing urandom";
+ my $s; read $F, $s, $nb;
+ $s = pack("b8" x $nb, unpack "b6" x $nb, $s);
+ $s =~ tr|\0-\x3f|0-9a-zA-Z./|;
+ $s;
+}
+
+sub user_crypted_passwd {
+ my ($u, $authentication) = @_;
+ if ($u->{password}) {
+ require utf8;
+ utf8::encode($u->{password}); #- we don't want perl to do "smart" things in crypt()
+
+ # Default to sha512
+ $authentication = { sha512 => 1 } unless $authentication;
+
+ my $salt;
+ if ($authentication->{blowfish}) {
+ $salt = '$2a$08$' . salt(60);
+ } elsif ($authentication->{md5}) {
+ $salt = '$1$' . salt(8);
+ } elsif ($authentication->{sha256}) {
+ $salt = '$5$' . salt(32);
+ } elsif ($authentication->{sha512}) {
+ $salt = '$6$' . salt(64);
+ } else {
+ $salt = salt(2);
+ }
+
+ crypt($u->{password}, $salt);
+ } else {
+ $u->{pw} || '';
+ }
+}
+
+sub set_root_passwd {
+ my ($superuser, $authentication) = @_;
+ $superuser->{name} = 'root';
+ write_passwd_user($superuser, $authentication);
+ delete $superuser->{name};
+}
+
+sub write_passwd_user {
+ my ($u, $authentication) = @_;
+
+ $u->{pw} = user_crypted_passwd($u, $authentication);
+ $u->{shell} ||= '/bin/bash';
+
+ substInFile {
+ my $l = unpack_passwd($_);
+ if ($l->{name} eq $u->{name}) {
+ add2hash_($u, $l);
+ $_ = pack_passwd($u);
+ $u = {};
+ }
+ if (eof && $u->{name}) {
+ $_ .= pack_passwd($u);
+ }
+ } "$::prefix/etc/passwd";
+}
+my @etc_pass_fields = qw(name pw uid gid realname home shell);
+sub unpack_passwd {
+ my ($l) = @_;
+ my %l; @l{@etc_pass_fields} = split ':', chomp_($l);
+ \%l;
+}
+sub pack_passwd {
+ my ($l) = @_;
+ join(':', @$l{@etc_pass_fields}) . "\n";
+}
+
+sub add_cafile() {
+ my $in = interactive->vnew;
+ $in->ask_filename({ title => N("Select file") }) or return;
+}
+
+sub auth() {
+ my $in = interactive->vnew;
+ $in->ask_from('', N(" "), [
+ { label => N("Domain Windows for authentication : ") . $authentication->{WINDOMAIN} },
+ {},
+ { label => N("Domain Admin User Name"), val => \$authentication->{winuser} },
+ { label => N("Domain Admin Password"), val => \$authentication->{winpass}, hidden => 1 },
+ ]);
+ return $authentication->{winuser}, $authentication->{winpass};
+}
+
+require fs::remote::smb;
+sub list_domains() {
+ my $smb = fs::remote::smb->new;
+ my %domains;
+ foreach my $server ($smb->find_servers) {
+ $domains{$server->{group}} = 1;
+ }
+ return sort keys %domains;
+}
+sub get_server_for_domain {
+ my $smb = fs::remote::smb->new;
+ foreach my $server ($smb->find_servers) {
+ return $server->{name} if $server->{group} == $_[0];
+ }
+}
+
+sub fetch_dn {
+ my ($srv) = @_;
+ my $s = run_program::rooted_get_stdout($::prefix, 'ldapsearch', '-x', '-h', $srv, '-b', '', '-s', 'base', '+');
+ $authentication->{LDAPDOMAIN} = first($s =~ /namingContexts: (.+)/);
+ return $authentication->{LDAPDOMAIN};
+}
+
+sub configure_nss_ldap {
+ my ($authentication) = @_;
+ update_ldap_conf(
+ uri => $authentication->{cafile} eq '1' ? "ldaps://" . $authentication->{LDAP_server} . "/" : "ldap://" . $authentication->{LDAP_server} . "/",
+ base => $authentication->{LDAPDOMAIN},
+ );
+
+ if ($authentication->{nssgrp} eq '1') {
+
+ update_ldap_conf(
+ 'base shadow' => $authentication->{nss_shadow},
+ 'base passwd' => $authentication->{nss_pwd},
+ 'base group' => $authentication->{nss_grp},
+ scope => "sub",
+ );
+ } else {
+
+ update_ldap_conf(
+ 'base shadow' => $authentication->{LDAPDOMAIN},
+ 'base passwd' => $authentication->{LDAPDOMAIN},
+ 'base group' => $authentication->{LDAPDOMAIN},
+ scope => "sub",
+ );
+ }
+ if ($authentication->{anonymous} eq '1') {
+ update_ldap_conf(
+ binddn => $authentication->{LDAP_binddn},
+ bindpw => $authentication->{LDAP_bindpwd},
+ );
+ }
+
+ if ($authentication->{cafile} eq '1') {
+ update_ldap_conf(
+ ssl => "on",
+ tls_reqcert => "allow",
+ tls_cacertfile => $authentication->{file},
+ );
+ }
+ }
+
+ sub compute_password_weakness {
+
+ my ($password) = @_;
+ my $score = 0;
+ my $len = length($password);
+
+ return 0 if $len == 0;
+
+ $score = $len < 5 ? 3 :
+ $len > 4 && $len < 8 ? 6 :
+ $len > 7 && $len < 16 ? 12 : 18;
+
+ $score += 1 if $password =~ /[a-z]/;
+ $score += 5 if $password =~ /[A-Z]/;
+ $score += 5 if $password =~ /\d+/;
+ $score += 5 if $password =~ /(.*[0-9].*[0-9].*[0-9])/;
+ $score += 5 if $password =~ /.[!@#$%^&*?_~,]/;
+ $score += 5 if $password =~ /(.*[!@#$%^&*?_~,].*[!@#$%^&*?_~,])/;
+ $score += 2 if $password =~ /([a-z].*[A-Z])|([A-Z].*[a-z])/;
+ $score += 2 if $password =~ /([a-zA-Z])/ && $password =~ /([0-9])/;
+ $score += 2 if $password =~ /([a-z].*[A-Z])|([A-Z].*[a-z])/;
+ $score += 2 if $password =~ /([a-zA-Z0-9].*[!@#$%^&*?_~])|([!@#$%^&*?_~,].*[a-zA-Z0-9])/;
+
+ my $level = $score < 11 ? 1 :
+ $score > 10 && $score < 20 ? 2 :
+ $score > 19 && $score < 30 ? 3 :
+ $score > 29 && $score < 40 ? 4 : 5;
+
+ return $level;
+ }
+1;