#!/usr/bin/perl
# 
# Copyright (c) 2007, 2008, 2009 Apple Inc. All rights reserved.
#
# @APPLE_LICENSE_HEADER_START@
#
# This file contains Original Code and/or Modifications of Original Code
# as defined in and that are subject to the Apple Public Source License
# Version 2.0 (the 'License'). You may not use this file except in
# compliance with the License. Please obtain a copy of the License at
# http://www.opensource.apple.com/apsl/ and read it before using this
# file.
#
# The Original Code and all software distributed under the License are
# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
# Please see the License for the specific language governing rights and
# limitations under the License.
#
# @APPLE_LICENSE_HEADER_END@
#
# This script generates a LocalKDC and provisions LKDC service principals.
# The script is non-destructive - it can be run multiple times.

use strict;
use File::Basename;

my $configured = "/var/db/.configureLocalKDC";

my $progname = basename ($0);

chdir '/' or die "chdir: $!\n";

if ($< != 0) {
	print 'Error: '. $progname ." needs to be run by root\n";
	exit 1;
}

umask 022;

close STDOUT;
open STDOUT, ">>/Library/Logs/LKDC-setup.log" || die "Failed to open STDOUT";
open STDERR, ">&STDOUT" || die "Failed to open STDERR";

# print when we are running
system("date");

my $system_keychain = '/Library/Keychains/System.keychain';

# certtool requires a system keychain to be present.  Bootstrap one if it is missing.
if (! -f $system_keychain) {
	system '/usr/sbin/systemkeychain', '-C';
}

my $kdc_dir = '/var/db/krb5kdc';
my $kdc_conf = $kdc_dir .'/kdc.conf';
my $kdc_local = '/var/db/dslocal/nodes/Default/config/KerberosKDC.plist';

my $plist_set = 0;
my $make_keytab_flag = 0;
my $update_plists_flag = 0;

my @kdcsetup_args;
my $krb5kdc_plist = '/System/Library/LaunchDaemons/edu.mit.Kerberos.krb5kdc.plist';
my $source_version = '10.5'; # if not given, assume Leopard
my $restore_directory;

my $fixup_disallow_service = 0;
my $create_keychain = 1;

my $argc = scalar @ARGV;
my $i;
for ($i = 0; $i < $argc; ++$i) {
	if ($ARGV[$i] eq '--plist') {
		$plist_set = 1;
		$make_keytab_flag = 0;
		$update_plists_flag = 1;
	} elsif ($ARGV[$i] eq '--target') {
		die "Error: --target requires an argument\n" unless ++$i < $argc;
		push @kdcsetup_args, '-T', $ARGV[$i];
	} elsif ($ARGV[$i] eq '--source') {
		die "Error: --source requires an argument\n" unless ++$i < $argc;
		$restore_directory = $ARGV[$i]
	} elsif ($ARGV[$i] eq '--source-version') {
		die "Error: --source-version requires an argument\n" unless ++$i < $argc;
		$source_version = $ARGV[$i]
	} elsif ($ARGV[$i] eq '--mode') {
		die "Error: --mode requires an argument\n" unless ++$i < $argc;
		# XXX handle migration assistant here
	} else {
		die "Error: unknown argument $ARGV[$i]\n";
	}
}

if (defined $restore_directory) {
    printf("Doing a migration\n");
    if ($source_version lt '10.5') {
	push @kdcsetup_args, '-L';
    } else {
	push @kdcsetup_args, '-R', $restore_directory;
	$fixup_disallow_service = 1;
	$create_keychain = 0;
	printf "Creating log directory\n";
	mkdir '/var/log/krb5kdc', 0700;
    }
} else {
    push @kdcsetup_args, '-L';
}

if (! -d $kdc_dir or ! -f $kdc_local) {
	my $res;

	if ($create_keychain) {
	    printf("creating system keychain entries\n");

	    $res = system '/usr/bin/certtool', 'C', 'com.apple.systemdefault', 'u', 'P', 'v';
	    if ($res != 0) {
		unlink $configured;
		die "cert tool failed for com.apple.systemdefault";
	    }
	    $res = system '/usr/bin/certtool', 'C', 'com.apple.kerberos.kdc', 'u', 'P', 'v';
	    if ($res != 0) {
		unlink $configured;
		die "cert tool failed for com.apple.kerberos.kdc";
	    }
	} else {
	    printf("system chain already migrated\n");
	}

	$res = system '/usr/sbin/kdcsetup', '-n', @kdcsetup_args;
	if ($res != 0) {
	    unlink $configured;
	    die "kdcsetup failed";
	}

	system '/bin/launchctl', 'load', $krb5kdc_plist;

	unless ($plist_set) {
		$make_keytab_flag = 1;
		$update_plists_flag = 1;
	}
}
system '/System/Library/PrivateFrameworks/KerberosHelper.framework/Resources/lkdc_acl',
     '-s', 'com.apple.kerberos.kdc', '-a', '/usr/sbin/krb5kdc';

if (not open (KDC_LOCAL, '/usr/libexec/PlistBuddy -c "Print :realname:" '. $kdc_local. '|')) {
	print 'Error: '. $progname ." failed to find $kdc_local\n";
	exit 1;
}

my $LKDC_realm = join ('', <KDC_LOCAL>);

$LKDC_realm =~ s/^.*(LKDC:SHA1\.[0-9A-F]{40}).*$/\1/s;

if ($LKDC_realm eq '') {
	print 'Error: '. $progname ." cannot parse $kdc_local\n";
	exit 1;
}

close KDC_LOCAL;

if (not open (KDC_CONF, '<', $kdc_conf)) {
	print 'Error: '. $progname ." failed to find $kdc_conf\n";
	exit 1;
}

my @default_realm = grep /default_realm/, <KDC_CONF>;

if (1 != scalar (@default_realm)) {
	print 'Error: '. $progname ." cannot parse $kdc_conf\n";
	exit 1;
}

my $default_realm = (split (/\s+/, $default_realm[0], 4))[3];
chomp $default_realm;

close KDC_CONF;

if ($default_realm !~ /^LKDC:/) {
	$update_plists_flag = 0;
}

if ($make_keytab_flag or $update_plists_flag) {
	my %afp_config = (service => 'afpserver', realm => $LKDC_realm,
			  prefs => '/Library/Preferences/com.apple.AppleFileServer',
			  key => 'kerberosPrincipal', format => '%s/%s@%2$s');

	my %smb_config = (service => 'cifs', realm => $LKDC_realm,
			  prefs => '/Library/Preferences/SystemConfiguration/com.apple.smb.server',
       			  key => 'LocalKerberosRealm', format => '%2$s');
 
	my %ss_config  = (service => 'vnc', realm => $LKDC_realm);

	configure_service (\%afp_config, $make_keytab_flag, $update_plists_flag);
	configure_service (\%smb_config, $make_keytab_flag, $update_plists_flag);
	configure_service (\%ss_config,  $make_keytab_flag, $update_plists_flag);
}

print $LKDC_realm . "\n";

if ($fixup_disallow_service) {
    my $IN;
    my @users;

    open IN, "/usr/sbin/kadmin.local -r ${LKDC_realm} -q 'listprincs *' -r ${LKDC_realm}|";
    while (<IN>) {
	next if (not m%^([^/ ]+)\@${LKDC_realm}%);
	push @users, $1;
    }
    close IN;

    foreach my $user (@users) {
	printf "fixing disable server for user: $user\n";
	system("/usr/sbin/kadmin.local ".
	       "-r ${LKDC_realm} ".
	       "-q \"modprinc -allow_svr +requires_preauth $user\@${LKDC_realm}\"");
    }
}

system "touch", $configured;

sub configure_service {
	my $config = shift;
	my $do_keytab = shift;
	my $do_plist  = shift;
	my $realm = $config->{realm};

	my $principal = sprintf "%s/%s@%s", $config->{service}, $realm, $realm;

	if ($do_keytab) {
		my $kadmin_local = '/usr/sbin/kadmin.local';

		my @args = ('add_principal', '-randkey', $principal);

		# kadmin.local is odd - the -q argument must be a single string argument
		print  $kadmin_local . join (' ', '-q', @args). "\n";
		system $kadmin_local, '-r', $LKDC_realm,
		       '-q', join (' ', @args);

		@args = ('ktadd', $principal);

		print  $kadmin_local . join (' ', '-q', @args). "\n";
		system $kadmin_local, '-r', $LKDC_realm,
		       '-q', join (' ', @args);
	} 

	if ($do_plist and defined ($config->{prefs})) {
		my $value = sprintf $config->{format}, $config->{service}, $realm;
		my @args = ('write', $config->{prefs}, $config->{key}, $value);

		print  '/usr/bin/defaults '. join (' ', @args). "\n";
		system '/usr/bin/defaults', @args;
	}
}
