xref: /illumos-gate/usr/src/cmd/krb5/kadmin/kclient/kclient.sh (revision 8fd04b8338ed5093ec2d1e668fa620b7de44c177)
1#!/bin/ksh93 -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23# Use is subject to license terms.
24#
25# This script is used to setup the Kerberos client by
26# supplying information about the Kerberos realm and kdc.
27#
28# The kerberos configuration file (/etc/krb5/krb5.conf) would
29# be generated and local host's keytab file setup. The script
30# can also optionally setup the system to do kerberized nfs and
31# bringover a master krb5.conf copy from a specified location.
32#
33
34function cleanup {
35
36	kdestroy -q > $TMP_FILE 2>&1
37	rm -r $TMPDIR > /dev/null 2>&1
38
39	exit $1
40}
41function exiting {
42
43        printf "\n$(gettext "Exiting setup, nothing changed").\n\n"
44
45	cleanup $1
46}
47
48function error_message {
49
50        printf -- "---------------------------------------------------\n" >&2
51        printf "$(gettext "Setup FAILED").\n\n" >&2
52
53        cleanup 1
54}
55
56function check_bin {
57
58	typeset bin=$1
59
60	if [[ ! -x $bin ]]; then
61		printf "$(gettext "Could not access/execute %s").\n" $bin >&2
62		error_message
63	fi
64}
65
66function cannot_create {
67	typeset filename="$1"
68	typeset stat="$2"
69
70	if [[ $stat -ne 0 ]]; then
71		printf "\n$(gettext "Can not create/edit %s, exiting").\n" $filename >&2
72		error_message
73	fi
74}
75
76function update_pam_conf {
77	typeset PAM TPAM service
78
79	PAM=/etc/pam.conf
80
81	TPAM=$(mktemp -q -t kclient-pamconf.XXXXXX)
82	if [[ -z $TPAM ]]; then
83		printf "\n$(gettext "Can not create temporary file, exiting").\n" >&2
84		error_message
85	fi
86
87	cp $PAM $TPAM >/dev/null 2>&1
88
89	printf "$(gettext "Configuring %s").\n\n" $PAM
90
91	for service in $SVCs; do
92		svc=${service%:*}
93		auth_type=${service#*:}
94		if egrep -s "^$svc[ 	][ 	]*auth.*pam_krb5*" $TPAM; then
95			printf "$(gettext "The %s service is already configured for pam_krb5, please merge this service in %s").\n\n" $svc $PAM >&2
96			continue
97		else
98			exec 3>>$TPAM
99			printf "\n$svc\tauth include\t\tpam_krb5_$auth_type\n" 1>&3
100		fi
101	done
102
103	cp $TPAM $PAM > /dev/null 2>&1
104
105	rm $TPAM > /dev/null 2>&1
106}
107
108function modify_nfssec_conf {
109	typeset NFSSEC_FILE="/etc/nfssec.conf"
110
111	if [[ -r $NFSSEC_FILE ]]; then
112		cat $NFSSEC_FILE > $NFSSEC_FILE.sav
113		cannot_create $NFSSEC_FILE.sav $?
114	fi
115
116	cat $NFSSEC_FILE > $TMP_FILE
117	cannot_create $TMP_FILE $?
118
119	if grep -s "#krb5" $NFSSEC_FILE > /dev/null 2>&1; then
120		sed "s%^#krb5%krb5%" $TMP_FILE >$NFSSEC_FILE
121		cannot_create $NFSSEC_FILE $?
122	fi
123}
124
125function call_kadmin {
126	typeset svc="$1"
127	typeset bool1 bool2 bool3 bool4
128	typeset service_princ getprincsubcommand anksubcommand ktaddsubcommand
129	typeset ktremsubcommand
130
131	for listentry in $fqdnlist; do
132
133	# Reset conditional vars to 1
134	bool1=1; bool2=1; bool3=1; bool4=1
135
136	service_princ=$(echo "${svc}/${listentry}")
137	getprincsubcommand="getprinc $service_princ"
138	anksubcommand="addprinc -randkey $service_princ"
139	ktaddsubcommand="ktadd $service_princ"
140	ktremsubcommand="ktrem $service_princ all"
141
142	kadmin -c $KRB5CCNAME -q "$getprincsubcommand" 1>$TMP_FILE 2>&1
143
144	egrep -s "$(gettext "get_principal: Principal does not exist")" $TMP_FILE
145	bool1=$?
146	egrep -s "$(gettext "get_principal: Operation requires ``get")" $TMP_FILE
147	bool2=$?
148
149	if [[ $bool1 -eq 0 || $bool2 -eq 0 ]]; then
150		kadmin -c $KRB5CCNAME -q "$anksubcommand" 1>$TMP_FILE 2>&1
151
152		egrep -s "$(gettext "add_principal: Principal or policy already exists while creating \"$service_princ@$realm\".")" $TMP_FILE
153		bool3=$?
154
155		egrep -s "$(gettext "Principal \"$service_princ@$realm\" created.")" $TMP_FILE
156		bool4=$?
157
158		if [[ $bool3 -eq 0 || $bool4 -eq 0 ]]; then
159			printf "$(gettext "%s entry ADDED to KDC database").\n" $service_princ
160		else
161			cat $TMP_FILE;
162			printf "\n$(gettext "kadmin: add_principal of %s failed, exiting").\n" $service_princ >&2
163			error_message
164		fi
165	else
166		printf "$(gettext "%s entry already exists in KDC database").\n" $service_princ >&2
167	fi
168
169	klist -k 1>$TMP_FILE 2>&1
170	egrep -s "$service_princ@$realm" $TMP_FILE
171	if [[ $? -eq 0 ]]; then
172		printf "$(gettext "%s entry already present in keytab").\n" $service_princ >&2
173		# Don't care is this succeeds or not, just need to replace old
174		# entries as it is assummed that the client is reinitialized
175		kadmin -c $KRB5CCNAME -q "$ktremsubcommand" 1>$TMP_FILE 2>&1
176	fi
177
178	kadmin -c $KRB5CCNAME -q "$ktaddsubcommand" 1>$TMP_FILE 2>&1
179	egrep -s "$(gettext "added to keytab WRFILE:$KRB5_KEYTAB_FILE.")" $TMP_FILE
180	if [[ $? -ne 0 ]]; then
181		cat $TMP_FILE;
182		printf "\n$(gettext "kadmin: ktadd of %s failed, exiting").\n" $service_princ >&2
183		error_message
184	else
185		printf "$(gettext "%s entry ADDED to keytab").\n" $service_princ
186	fi
187
188	done
189}
190
191function writeup_krb5_conf {
192	typeset dh
193
194	printf "\n$(gettext "Setting up %s").\n\n" $KRB5_CONFIG_FILE
195
196	exec 3>$KRB5_CONFIG
197	if [[ $? -ne 0 ]]; then
198		printf "\n$(gettext "Can not write to %s, exiting").\n" $KRB5_CONFIG >&2
199		error_message
200	fi
201
202	printf "[libdefaults]\n" 1>&3
203	if [[ $no_keytab == yes ]]; then
204		printf "\tverify_ap_req_nofail = false\n" 1>&3
205	fi
206	if [[ $dns_lookup == yes ]]; then
207	    printf "\t$dnsarg = on\n" 1>&3
208	    if [[ $dnsarg == dns_lookup_kdc ]]; then
209		printf "\tdefault_realm = $realm\n" 1>&3
210		printf "\n[domain_realm]\n" 1>&3
211		if [[ -n $fkdc_list ]]; then
212			for kdc in $fkdc_list; do
213				printf "\t$kdc = $realm\n" 1>&3
214			done
215		fi
216		printf "\t$FKDC = $realm\n" 1>&3
217		printf "\t$client_machine = $realm\n" 1>&3
218		if [[ -z $short_fqdn ]]; then
219			printf "\t.$domain = $realm\n\n" 1>&3
220		else
221			printf "\t.$short_fqdn = $realm\n\n" 1>&3
222		fi
223		if [[ -n $domain_list ]]; then
224			for dh in $domain_list; do
225				printf "\t$dh = $realm\n" 1>&3
226			done
227		fi
228	    else
229		if [[ $dnsarg = dns_lookup_realm ]]; then
230		    printf "\tdefault_realm = $realm\n" 1>&3
231		    printf "\n[realms]\n" 1>&3
232		    printf "\t$realm = {\n" 1>&3
233		    if [[ -n $kdc_list ]]; then
234			for kdc in $kdc_list; do
235				printf "\t\tkdc = $kdc\n" 1>&3
236			done
237		    else
238		    	printf "\t\tkdc = $KDC\n" 1>&3
239		    fi
240		    printf "\t\tadmin_server = $KDC\n" 1>&3
241		    if [[ $non_solaris == yes ]]; then
242			printf "\n\t\tkpasswd_protocol = SET_CHANGE\n" 1>&3
243		    fi
244		    printf "\t}\n\n" 1>&3
245		else
246		    printf "\tdefault_realm = $realm\n\n" 1>&3
247		fi
248	    fi
249	else
250	    printf "\tdefault_realm = $realm\n\n" 1>&3
251
252	    printf "[realms]\n" 1>&3
253	    printf "\t$realm = {\n" 1>&3
254	    if [[ -n $kdc_list ]]; then
255		for kdc in $kdc_list; do
256			printf "\t\tkdc = $kdc\n" 1>&3
257		done
258	    else
259	    	printf "\t\tkdc = $KDC\n" 1>&3
260	    fi
261	    printf "\t\tadmin_server = $KDC\n" 1>&3
262	    if [[ $non_solaris == yes ]]; then
263	    	printf "\n\t\tkpasswd_protocol = SET_CHANGE\n" 1>&3
264	    fi
265	    printf "\t}\n\n" 1>&3
266
267	    printf "[domain_realm]\n" 1>&3
268	    if [[ -n $fkdc_list ]]; then
269		for kdc in $fkdc_list; do
270			printf "\t$kdc = $realm\n" 1>&3
271		done
272	    fi
273	    printf "\t$FKDC = $realm\n" 1>&3
274	    printf "\t$client_machine = $realm\n" 1>&3
275	    if [[ -z $short_fqdn ]]; then
276		printf "\t.$domain = $realm\n\n" 1>&3
277	    else
278		printf "\t.$short_fqdn = $realm\n\n" 1>&3
279	    fi
280	    if [[ -n $domain_list ]]; then
281		for dh in $domain_list; do
282			printf "\t$dh = $realm\n" 1>&3
283		done
284	    fi
285	fi
286
287	printf "[logging]\n" 1>&3
288	printf "\tdefault = FILE:/var/krb5/kdc.log\n" 1>&3
289	printf "\tkdc = FILE:/var/krb5/kdc.log\n" 1>&3
290	printf "\tkdc_rotate = {\n\t\tperiod = 1d\n\t\tversions = 10\n\t}\n\n" 1>&3
291
292	printf "[appdefaults]\n" 1>&3
293	printf "\tkinit = {\n\t\trenewable = true\n\t\tforwardable = true\n" 1>&3
294	if [[ $no_keytab == yes ]]; then
295		printf "\t\tno_addresses = true\n" 1>&3
296	fi
297	printf "\t}\n" 1>&3
298}
299
300function ask {
301	typeset question=$1
302	typeset default_answer=$2
303
304	if [[ -z $default_answer ]]; then
305		printf "$question :"
306	else
307		printf "$question [$default_answer]: "
308	fi
309	read answer
310	test -z "$answer" && answer="$default_answer"
311}
312
313function yesno {
314	typeset question="$1"
315
316	answer=
317	yn=`printf "$(gettext "y/n")"`
318	y=`printf "$(gettext "y")"`
319	n=`printf "$(gettext "n")"`
320	yes=`printf "$(gettext "yes")"`
321	no=`printf "$(gettext "no")"`
322
323	while [[ -z $answer ]]; do
324		ask "$question" $yn
325		case $answer in
326			$y|$yes)	answer=yes;;
327			$n|$no)		answer=no;;
328			*)		answer=;;
329		esac
330	done
331}
332
333function query {
334	yesno "$*"
335
336	if [[ $answer == no ]]; then
337		printf "\t$(gettext "No action performed").\n"
338	fi
339}
340
341
342function read_profile {
343	typeset param value
344	typeset file="$1"
345
346	if [[ ! -d $file && -r $file ]]; then
347		while read param value
348		do
349			case $param in
350			REALM)  if [[ -z $realm ]]; then
351					realm="$value"
352					checkval="REALM"; check_value $realm
353				fi
354				;;
355			KDC)    if [[ -z $KDC ]]; then
356					KDC="$value"
357					checkval="KDC"; check_value $KDC
358				fi
359				;;
360			ADMIN)  if [[ -z $ADMIN_PRINC ]]; then
361					ADMIN_PRINC="$value"
362					checkval="ADMIN_PRINC"
363    					check_value $ADMIN_PRINC
364				fi
365				;;
366			FILEPATH)  if [[ -z $filepath ]]; then
367					filepath="$value"
368				   fi
369				   ;;
370			NFS)    if [[ -z $add_nfs ]]; then
371				    if [[ $value == 1 ]]; then
372					    add_nfs=yes
373				    else
374					    add_nfs=no
375				    fi
376				fi
377				;;
378			NOKEY)    if [[ -z $no_keytab ]]; then
379				    if [[ $value == 1 ]]; then
380					    no_keytab=yes
381				    else
382					    no_keytab=no
383				    fi
384				fi
385				;;
386			NOSOL)  if [[ -z $non_solaris ]]; then
387				    if [[ $value == 1 ]]; then
388					    non_solaris=yes
389					    no_keytab=yes
390				    else
391					    non_solaris=no
392				    fi
393				fi
394				;;
395			LHN)    if [[ -z $logical_hn ]]; then
396					logical_hn="$value"
397					checkval="LOGICAL_HOSTNAME"
398    					check_value $logical_hn
399				fi
400				;;
401			DNSLOOKUP) if [[ -z $dnsarg ]]; then
402					dnsarg="$value"
403					checkval="DNS_OPTIONS"
404					check_value $dnsarg
405				   fi
406				   ;;
407			FQDN) if [[ -z $fqdnlist ]]; then
408					fqdnlist="$value"
409					checkval="FQDN"
410					check_value $fqdnlist
411					verify_fqdnlist "$fqdnlist"
412			      fi
413			      ;;
414			MSAD) if [[ -z $msad ]]; then
415				if [[ $value == 1 ]]; then
416					msad=yes
417					non_solaris=yes
418				else
419					msad=no
420				fi
421			      fi
422			      ;;
423			esac
424		done <$file
425	else
426		printf "\n$(gettext "The kclient profile \`%s' is not valid, exiting").\n" $file >&2
427		error_message
428	fi
429}
430
431function ping_check {
432	typeset machine="$1"
433	typeset string="$2"
434
435	if ping $machine 2 > /dev/null 2>&1; then
436		:
437	else
438		printf "\n$(gettext "%s %s is unreachable, exiting").\n" $string $machine >&2
439		error_message
440	fi
441
442	# Output timesync warning if not using a profile, i.e. in
443	# interactive mode.
444	if [[ -z $profile && $string == KDC ]]; then
445		# It's difficult to sync up time with KDC esp. if in a
446		# zone so just print a warning about KDC time sync.
447		printf "\n$(gettext "Note, this system and the KDC's time must be within 5 minutes of each other for Kerberos to function").\n" >&2
448		printf "$(gettext "Both systems should run some form of time synchronization system like Network Time Protocol (NTP)").\n" >&2
449break
450	fi
451}
452
453function check_value {
454	typeset arg="$1"
455
456	if [[ -z $arg ]]; then
457		printf "\n$(gettext "No input obtained for %s, exiting").\n" $checkval >&2
458		error_message
459	else
460		echo "$arg" > $TMP_FILE
461		if egrep -s '[*$^#!]+' $TMP_FILE; then
462			printf "\n$(gettext "Invalid input obtained for %s, exiting").\n" $checkval >&2
463			error_message
464		fi
465	fi
466}
467
468function set_dns_value {
469	typeset -l arg="$1"
470
471	if [[ $arg == dns_lookup_kdc  ||  $arg == dns_lookup_realm  || $arg == dns_fallback ]]; then
472		dns_lookup=yes
473	else
474		if [[ $arg == none ]]; then
475			dns_lookup=no
476		else
477			printf "\n$(gettext "Invalid DNS lookup option, exiting").\n" >&2
478			error_message
479		fi
480	fi
481}
482
483function verify_kdcs {
484	typeset k_list="$1"
485	typeset -l kdc
486	typeset list fqhn f_list
487
488	kdc_list=$(echo "$k_list" | sed 's/,/ /g')
489
490	if [[ -z $k_list ]]; then
491		printf "\n$(gettext "At least one KDC should be listed").\n\n" >&2
492		usage
493	fi
494
495	for kdc in $k_list; do
496		if [[ $kdc != $KDC ]]; then
497			list="$list $kdc"
498			fkdc=`$KLOOKUP $kdc`
499			if ping $fkdc 2 > /dev/null; then
500				:
501			else
502				printf "\n$(gettext "%s %s is unreachable, no action performed").\n" "KDC" $fkdc >&2
503			fi
504			f_list="$f_list $fkdc"
505		fi
506	done
507
508	fkdc_list="$f_list"
509	kdc_list="$list"
510}
511
512function parse_service {
513	typeset service_list=$1
514
515	service_list=${service_list//,/ }
516	for service in $service_list; do
517		svc=${service%:}
518		auth_type=${service#:}
519		[[ -z $svc || -z $auth_type ]] && return
520		print -- $svc $auth_type
521	done
522}
523
524function verify_fqdnlist {
525	typeset list="$1"
526	typeset -l hostname
527	typeset -i count=1
528	typeset fqdnlist eachfqdn tmpvar fullhost
529
530	list=$(echo "$list" | tr -d " " | tr -d "\t")
531	hostname=$(uname -n | cut -d"." -f1)
532	fqdnlist=$client_machine
533
534	eachfqdn=$(echo "$list" | cut -d"," -f$count)
535	if [[ -z $eachfqdn ]]; then
536		printf "\n$(gettext "If the -f option is used, at least one FQDN should be listed").\n\n" >&2
537		usage
538	else
539		while [[ ! -z $eachfqdn ]]; do
540			tmpvar=$(echo "$eachfqdn" | cut -d"." -f1)
541			if [[ -z $tmpvar ]]; then
542				fullhost="$hostname$eachfqdn"
543			else
544				fullhost="$hostname.$eachfqdn"
545			fi
546
547			ping_check $fullhost $(gettext "System")
548			if [[ $fullhost == $client_machine ]]; then
549				:
550			else
551				fqdnlist="$fqdnlist $fullhost"
552			fi
553
554			if [[ $list == *,* ]]; then
555				((count = count + 1))
556				eachfqdn=$(echo "$list" | cut -d"," -f$count)
557			else
558				break
559			fi
560		done
561	fi
562}
563
564function setup_keytab {
565	typeset cname ask_fqdns current_release
566
567	#
568	# 1. kinit with ADMIN_PRINC
569	#
570
571	if [[ -z $ADMIN_PRINC ]]; then
572		printf "\n$(gettext "Enter the krb5 administrative principal to be used"): "
573		read ADMIN_PRINC
574		checkval="ADMIN_PRINC"; check_value $ADMIN_PRINC
575	fi
576
577	echo "$ADMIN_PRINC">$TMP_FILE
578
579	[[ -n $msad ]] && return
580	if egrep -s '\/admin' $TMP_FILE; then
581		# Already in "/admin" format, do nothing
582		:
583	else
584		if egrep -s '\/' $TMP_FILE; then
585			printf "\n$(gettext "Improper entry for krb5 admin principal, exiting").\n" >&2
586			error_message
587		else
588			ADMIN_PRINC=$(echo "$ADMIN_PRINC/admin")
589		fi
590	fi
591
592	printf "$(gettext "Obtaining TGT for %s") ...\n" $ADMIN_PRINC
593
594	cname=$(canon_resolve $KDC)
595	if [[ -n $cname ]]; then
596		kinit -S kadmin/$cname $ADMIN_PRINC
597	else
598		kinit -S kadmin/$FKDC $ADMIN_PRINC
599	fi
600	klist 1>$TMP_FILE 2>&1
601	if egrep -s "$(gettext "Valid starting")" $TMP_FILE && egrep -s "kadmin/$FKDC@$realm" $TMP_FILE; then
602    		:
603	else
604		printf "\n$(gettext "kinit of %s failed, exiting").\n" $ADMIN_PRINC >&2
605		error_message
606	fi
607
608	#
609	# 2. Do we want to create and/or add service principal(s) for fqdn's
610	#    other than the one listed in resolv.conf(4) ?
611	#
612	if [[ -z $options ]]; then
613		query "$(gettext "Do you have multiple DNS domains spanning the Kerberos realm") $realm ?"
614		ask_fqdns=$answer
615		if [[ $ask_fqdns == yes ]]; then
616			printf "$(gettext "Enter a comma-separated list of DNS domain names"): "
617			read fqdnlist
618			verify_fqdnlist "$fqdnlist"
619		else
620			fqdnlist=$client_machine
621		fi
622	else
623		if [[ -z $fqdnlist ]]; then
624			fqdnlist=$client_machine
625		fi
626	fi
627
628	if [[ $add_nfs == yes ]]; then
629		echo; call_kadmin nfs
630	fi
631
632	# Add the host entry to the keytab
633	echo; call_kadmin host
634
635}
636
637function setup_lhn {
638	typeset -l logical_hn
639
640	echo "$logical_hn" > $TMP_FILE
641	if egrep -s '[^.]\.[^.]+$' $TMP_FILE; then
642		# do nothing, logical_hn is in fqdn format
643		:
644	else
645		if egrep -s '\.+' $TMP_FILE; then
646			printf "\n$(gettext "Improper format of logical hostname, exiting").\n" >&2
647			error_message
648		else
649			# Attach fqdn to logical_hn, to get the Fully Qualified
650			# Host Name of the client requested
651			logical_hn=$(echo "$logical_hn.$fqdn")
652		fi
653	fi
654
655	client_machine=$logical_hn
656
657	ping_check $client_machine $(gettext "System")
658}
659
660function usage {
661	printf "\n$(gettext "Usage: kclient [ options ]")\n" >&2
662	printf "\t$(gettext "where options are any of the following")\n\n" >&2
663	printf "\t$(gettext "[ -D domain_list ]  configure a client that has mul
664tiple mappings of doamin and/or hosts to the default realm")\n" >&2
665	printf "\t$(gettext "[ -K ]  configure a client that does not have host/service keys")\n" >&2
666	printf "\t$(gettext "[ -R realm ]  specifies the realm to use")\n" >&2
667	printf "\t$(gettext "[ -T kdc_vendor ]  specifies which KDC vendor is the server")\n" >&2
668	printf "\t$(gettext "[ -a adminuser ]  specifies the Kerberos administrator")\n" >&2
669	printf "\t$(gettext "[ -c filepath ]  specifies the krb5.conf path used to configure this client")\n" >&2
670	printf "\t$(gettext "[ -d dnsarg ]  specifies which information should be looked up in DNS (dns_lookup_kdc, dns_lookup_realm, and dns_fallback)")\n" >&2
671	printf "\t$(gettext "[ -f fqdn_list ]  specifies which domains to configure host keys for this client")\n" >&2
672	printf "\t$(gettext "[ -h logicalhostname ]  configure the logical host name for a client that is in a cluster")\n" >&2
673	printf "\t$(gettext "[ -k kdc_list ]  specify multiple KDCs, if -m is not used the first KDC in the list is assumed to be the master.  KDC host names are used verbatim.")\n" >&2
674	printf "\t$(gettext "[ -m master ]  master KDC server host name")\n" >&2
675	printf "\t$(gettext "[ -n ]  configure client to be an NFS client")\n" >&2
676	printf "\t$(gettext "[ -p profile ]  specifies which profile file to use to configure this client")\n" >&2
677	printf "\t$(gettext "[ -s pam_list ]  update the service for Kerberos authentication")\n" >&2
678	error_message
679}
680
681function discover_domain {
682	typeset dom DOMs
683
684	if [[ -z $realm ]]; then
685		set -A DOMs -- `$KLOOKUP _ldap._tcp.dc._msdcs S`
686	else
687		set -A DOMs -- `$KLOOKUP _ldap._tcp.dc._msdcs.$realm S`
688	fi
689
690	[[ -z ${DOMs[0]} ]] && return 1
691
692	dom=${DOMs[0]}
693
694	dom=${dom#*.}
695	dom=${dom% *}
696
697	domain=$dom
698
699	return 0
700}
701
702function check_nss_hosts_or_ipnodes_config {
703	typeset backend
704
705	for backend in $1
706	do
707		[[ $backend == dns ]] && return 0
708	done
709	return 1
710}
711
712function check_nss_conf {
713	typeset i j hosts_config
714
715	for i in hosts ipnodes
716	do
717		grep "^${i}:" /etc/nsswitch.conf|read j hosts_config
718		check_nss_hosts_or_ipnodes_config "$hosts_config" || return 1
719	done
720
721	return 0
722}
723
724function canon_resolve {
725	typeset name ip
726
727	name=`$KLOOKUP $1 C`
728	[[ -z $name ]] && name=`$KLOOKUP $1 A`
729	[[ -z $name ]] && return
730
731	ip=`$KLOOKUP $name I`
732	[[ -z $ip ]] && return
733	for i in $ip
734	do
735		if ping $i 2 > /dev/null 2>&1; then
736			break
737		else
738			i=
739		fi
740	done
741
742	cname=`$KLOOKUP $ip P`
743	[[ -z $cname ]] && return
744
745	print -- "$cname"
746}
747
748function rev_resolve {
749	typeset name ip
750
751	ip=`$KLOOKUP $1 I`
752
753	[[ -z $ip ]] && return
754	name=`$KLOOKUP $ip P`
755	[[ -z $name ]] && return
756
757	print -- $name
758}
759
760# Convert an AD-style domain DN to a DNS domainname
761function dn2dns {
762	typeset OIFS dname dn comp components
763
764	dn=$1
765	dname=
766
767	OIFS="$IFS"
768	IFS=,
769	set -A components -- $1
770	IFS="$OIFS"
771
772	for comp in "${components[@]}"
773	do
774		[[ "$comp" == [dD][cC]=* ]] || continue
775		dname="$dname.${comp#??=}"
776	done
777
778	print ${dname#.}
779}
780
781# Form a base DN from a DNS domainname and container
782function getBaseDN {
783	if [[ -n "$2" ]]
784	then
785		baseDN="CN=$1,$(dns2dn $2)"
786	else
787		baseDN="$(dns2dn $2)"
788	fi
789}
790
791# Convert a DNS domainname to an AD-style DN for that domain
792function dns2dn {
793	typeset OIFS dn labels
794
795	OIFS="$IFS"
796	IFS=.
797	set -A labels -- $1
798	IFS="$OIFS"
799
800	dn=
801	for label in "${labels[@]}"
802	do
803		dn="${dn},DC=$label"
804	done
805
806	print -- "${dn#,}"
807}
808
809function getSRVs {
810	typeset srv port
811
812	$KLOOKUP $1 S | while read srv port
813	do
814		if ping $srv 2 > /dev/null 2>&1; then
815			print -- $srv $port
816		fi
817	done
818}
819
820function getKDC {
821	typeset j
822
823	set -A KPWs -- $(getSRVs _kpasswd._tcp.$dom.)
824	kpasswd=${KPWs[0]}
825
826	if [[ -n $siteName ]]
827	then
828		set -A KDCs -- $(getSRVs _kerberos._tcp.$siteName._sites.$dom.)
829		kdc=${KDCs[0]}
830		[[ -n $kdc ]] && return
831	fi
832
833	# No site name
834	set -A KDCs -- $(getSRVs _kerberos._tcp.$dom.)
835	kdc=${KDCs[0]}
836	[[ -n $kdc ]] && return
837
838	# Default
839	set -A KDCs -- $DomainDnsZones 88
840	kdc=$ForestDnsZones
841}
842
843function getDC {
844	typeset j
845
846	if [[ -n $siteName ]]
847	then
848		set -A DCs -- $(getSRVs _ldap._tcp.$siteName._sites.dc._msdcs.$dom.)
849		dc=${DCs[0]}
850		[[ -n $dc ]] && return
851	fi
852
853	# No site name
854	set -A DCs -- $(getSRVs _ldap._tcp.dc._msdcs.$dom.)
855	dc=${DCs[0]}
856	[[ -n $dc ]] && return
857
858	# Default
859	set -A DCs -- $DomainDnsZones 389
860	dc=$DomainDnsZones
861}
862
863function write_ads_krb5conf {
864	printf "\n$(gettext "Setting up %s").\n\n" $KRB5_CONFIG_FILE
865
866	exec 3>$KRB5_CONFIG
867	if [[ $? -ne 0 ]]; then
868		printf "\n$(gettext "Can not write to %s, exiting").\n" $KRB5_CONFIG >&2
869		error_message
870	fi
871
872	printf "[libdefaults]\n" 1>&3
873	printf "\tdefault_realm = $realm\n" 1>&3
874	printf "\n[realms]\n" 1>&3
875	printf "\t$realm = {\n" 1>&3
876	for i in ${KDCs[@]}
877	do
878		[[ $i == +([0-9]) ]] && continue
879		printf "\t\tkdc = $i\n" 1>&3
880	done
881	# Defining the same as admin_server.  This would cause auth failures
882	# if this was different.
883	printf "\n\t\tkpasswd_server = $KDC\n" 1>&3
884	printf "\n\t\tadmin_server = $KDC\n" 1>&3
885	printf "\t\tkpasswd_protocol = SET_CHANGE\n\t}\n" 1>&3
886	printf "\n[domain_realm]\n" 1>&3
887	printf "\t.$dom = $realm\n\n" 1>&3
888	printf "[logging]\n" 1>&3
889	printf "\tdefault = FILE:/var/krb5/kdc.log\n" 1>&3
890	printf "\tkdc = FILE:/var/krb5/kdc.log\n" 1>&3
891	printf "\tkdc_rotate = {\n\t\tperiod = 1d\n\t\tversions = 10\n\t}\n\n" 1>&3
892	printf "[appdefaults]\n" 1>&3
893	printf "\tkinit = {\n\t\trenewable = true\n\t\tforwardable = true\n\t}\n" 1>&3
894}
895
896function getForestName {
897	ldapsearch -R -T -h $dc $ldap_args \
898	    -b "" -s base "" schemaNamingContext| \
899		grep ^schemaNamingContext|read j schemaNamingContext
900
901	if [[ $? -ne 0 ]]; then
902		printf "$(gettext "Can't find forest").\n" >&2
903		error_message
904	fi
905	schemaNamingContext=${schemaNamingContext#CN=Schema,CN=Configuration,}
906
907	[[ -z $schemaNamingContext ]] && return 1
908
909	forest=
910	while [[ -n $schemaNamingContext ]]
911	do
912		schemaNamingContext=${schemaNamingContext#DC=}
913		forest=${forest}.${schemaNamingContext%%,*}
914		[[ "$schemaNamingContext" = *,* ]] || break
915		schemaNamingContext=${schemaNamingContext#*,}
916	done
917	forest=${forest#.}
918}
919
920function getGC {
921	typeset j
922
923	[[ -n $gc ]] && return 0
924
925	if [[ -n $siteName ]]
926	then
927		set -A GCs -- $(getSRVs _ldap._tcp.$siteName._sites.gc._msdcs.$forest.)
928		gc=${GCs[0]}
929		[[ -n $gc ]] && return
930	fi
931
932	# No site name
933	set -A GCs -- $(getSRVs _ldap._tcp.gc._msdcs.$forest.)
934	gc=${GCs[0]}
935	[[ -n $gc ]] && return
936
937	# Default
938	set -A GCs -- $ForestDnsZones 3268
939	gc=$ForestDnsZones
940}
941
942#
943# The local variables used to calculate the IP address are of type unsigned
944# integer (-ui), as this is required to restrict the integer to 32b.
945# Starting in ksh88, Solaris has incorrectly assummed that -i represents 64b.
946#
947function ipAddr2num {
948	typeset OIFS
949	typeset -ui16 num
950
951	if [[ "$1" != +([0-9]).+([0-9]).+([0-9]).+([0-9]) ]]
952	then
953		print 0
954		return 0
955	fi
956
957	OIFS="$IFS"
958	IFS=.
959	set -- $1
960	IFS="$OIFS"
961
962	num=$((${1}<<24 | ${2}<<16 | ${3}<<8 | ${4}))
963
964	print -- $num
965}
966
967#
968# The local variables used to calculate the IP address are of type unsigned
969# integer (-ui), as this is required to restrict the integer to 32b.
970# Starting in ksh88, Solaris has incorrectly assummed that -i represents 64b.
971#
972function num2ipAddr {
973	typeset -ui16 num
974	typeset -ui10 a b c d
975
976	num=$1
977	a=$((num>>24        ))
978	b=$((num>>16 & 16#ff))
979	c=$((num>>8  & 16#ff))
980	d=$((num     & 16#ff))
981	print -- $a.$b.$c.$d
982}
983
984#
985# The local variables used to calculate the IP address are of type unsigned
986# integer (-ui), as this is required to restrict the integer to 32b.
987# Starting in ksh88, Solaris has incorrectly assummed that -i represents 64b.
988#
989function netmask2length {
990	typeset -ui16 netmask
991	typeset -i len
992
993	netmask=$1
994	len=32
995	while [[ $((netmask % 2)) -eq 0 ]]
996	do
997		netmask=$((netmask>>1))
998		len=$((len - 1))
999	done
1000	print $len
1001}
1002
1003#
1004# The local variables used to calculate the IP address are of type unsigned
1005# integer (-ui), as this is required to restrict the integer to 32b.
1006# Starting in ksh88, Solaris has incorrectly assummed that -i represents 64b.
1007#
1008function getSubnets {
1009	typeset -ui16 addr netmask
1010	typeset -ui16 classa=16\#ff000000
1011
1012	ifconfig -a|while read line
1013	do
1014		addr=0
1015		netmask=0
1016		set -- $line
1017		[[ $1 == inet ]] || continue
1018		while [[ $# -gt 0 ]]
1019		do
1020			case "$1" in
1021				inet) addr=$(ipAddr2num $2); shift;;
1022				netmask) eval netmask=16\#$2; shift;;
1023				*) :;
1024			esac
1025			shift
1026		done
1027
1028		[[ $addr -eq 0 || $netmask -eq 0 ]] && continue
1029		[[ $((addr & classa)) -eq 16\#7f000000 ]] && continue
1030
1031		print $(num2ipAddr $((addr & netmask)))/$(netmask2length $netmask)
1032	done
1033}
1034
1035function getSite {
1036	typeset subnet siteDN j ldapsrv subnet_dom
1037
1038	eval "[[ -n \"\$siteName\" ]]" && return
1039	for subnet in $(getSubnets)
1040	do
1041		ldapsearch -R -T -h $dc $ldap_args \
1042		    -p 3268 -b "" -s sub cn=$subnet dn |grep ^dn|read j subnetDN
1043
1044		[[ -z $subnetDN ]] && continue
1045		subnet_dom=$(dn2dns $subnetDN)
1046		ldapsrv=$(canon_resolve DomainDnsZones.$subnet_dom)
1047		[[ -z $ldapsrv ]] && continue
1048		ldapsearch -R -T -h $ldapsrv $ldap_args \
1049		    -b "$subnetDN" -s base "" siteObject \
1050		    |grep ^siteObject|read j siteDN
1051
1052		[[ -z $siteDN ]] && continue
1053
1054		eval siteName=${siteDN%%,*}
1055		eval siteName=\${siteName#CN=}
1056		return
1057	done
1058}
1059
1060function doKRB5config {
1061	[[ -f $KRB5_CONFIG_FILE ]] && \
1062		cp $KRB5_CONFIG_FILE ${KRB5_CONFIG_FILE}-pre-kclient
1063
1064	[[ -f $KRB5_KEYTAB_FILE ]] && \
1065		cp $KRB5_KEYTAB_FILE ${KRB5_KEYTAB_FILE}-pre-kclient
1066
1067	[[ -s $KRB5_CONFIG ]] && cp $KRB5_CONFIG $KRB5_CONFIG_FILE
1068	[[ -s $KRB5_CONFIG_FILE ]] && chmod 0644 $KRB5_CONFIG_FILE
1069	[[ -s $new_keytab ]] && cp $new_keytab $KRB5_KEYTAB_FILE
1070	[[ -s $KRB5_KEYTAB_FILE ]] && chmod 0600 $KRB5_KEYTAB_FILE
1071}
1072
1073function addDNSRR {
1074	smbFMRI=svc:/network/smb/server:default
1075	ddnsProp=smbd/ddns_enable
1076	enProp=general/enabled
1077
1078	enabled=`svcprop -p $enProp $smbFMRI`
1079	ddns_enable=`svcprop -p $ddnsProp $smbFMRI`
1080
1081	if [[ $enabled == true && $ddns_enable != true ]]; then
1082		printf "$(gettext "Warning: won't create DNS records for client").\n"
1083		printf "$(gettext "%s property not set to 'true' for the %s FMRI").\n" $ddnsProp $smbFMRI
1084		return
1085	fi
1086
1087	# Destroy any existing ccache as GSS_C_NO_CREDENTIAL will pick up any
1088	# residual default credential in the cache.
1089	kdestroy > /dev/null 2>&1
1090
1091	$KDYNDNS -d $1 > /dev/null 2>&1
1092	if [[ $? -ne 0 ]]; then
1093		#
1094		# Non-fatal, we should carry-on as clients may resolve to
1095		# different servers and the client could already exist there.
1096		#
1097		printf "$(gettext "Warning: unable to create DNS records for client").\n"
1098		printf "$(gettext "This could mean that '%s' is not included as a 'nameserver' in the /etc/resolv.conf file or some other type of error").\n" $dc
1099	fi
1100}
1101
1102function setSMB {
1103	typeset domain=$1
1104	typeset server=$2
1105	smbFMRI=svc:/network/smb/server
1106
1107	printf "%s" "$newpw" | $KSMB -d $domain -s $server
1108	if [[ $? -ne 0 ]]; then
1109		printf "$(gettext "Warning: unable to set %s domain, server and password information").\n" $smbFMRI
1110		return
1111	fi
1112
1113	svcadm restart $smbFMRI > /dev/null 2>&1
1114	if [[ $? -ne 0 ]]; then
1115		printf "$(gettext "Warning: unable to restart %s").\n" $smbFMRI
1116	fi
1117}
1118
1119function compareDomains {
1120	typeset oldDom hspn newDom=$1
1121
1122	# If the client has been previously configured in a different
1123	# realm/domain then we need to prompt the user to see if they wish to
1124	# switch domains.
1125	klist -k 2>&1 | grep @ | read j hspn
1126	[[ -z $hspn ]] && return
1127
1128	oldDom=${hspn#*@}
1129	if [[ $oldDom != $newDom ]]; then
1130		printf "$(gettext "The client is currently configured in a different domain").\n"
1131		printf "$(gettext "Currently in the '%s' domain, trying to join the '%s' domain").\n" $oldDom $newDom
1132		query "$(gettext "Do you want the client to join a new domain") ?"
1133		printf "\n"
1134		if [[ $answer != yes ]]; then
1135			printf "$(gettext "Client will not be joined to the new domain").\n" >&2
1136			error_message
1137		fi
1138	fi
1139}
1140
1141function getKDCDC {
1142
1143	getKDC
1144	if [[ -n $kdc ]]; then
1145		KDC=$kdc
1146		dc=$kdc
1147	else
1148		getDC
1149		if [[ -n $dc ]]; then
1150			KDC=$dc
1151		else
1152			printf "$(gettext "Could not find domain controller server for '%s'.  Exiting").\n" $realm >&2
1153			error_message
1154		fi
1155	fi
1156}
1157
1158function gen_rand {
1159	typeset -u hex
1160
1161	dd if=/dev/random bs=1 count=1 2>/dev/null | od -A n -tx1 | read hex
1162
1163	printf %s $((16#$hex))
1164}
1165
1166function join_domain {
1167	typeset -u upcase_nodename
1168	typeset -l locase_nodename
1169	typeset -L15 string15
1170	typeset netbios_nodename fqdn
1171
1172	container=Computers
1173	ldap_args="-o authzid= -o mech=gssapi"
1174	userAccountControlBASE=4096
1175
1176	if [[ -z $ADMIN_PRINC ]]; then
1177		cprinc=Administrator
1178	else
1179		cprinc=$ADMIN_PRINC
1180	fi
1181
1182	if ! discover_domain; then
1183		printf "$(gettext "Can not find realm") '%s'.\n" $realm >&2
1184		error_message
1185	fi
1186
1187	dom=$domain
1188	realm=$domain
1189
1190	if [[ ${#hostname} -gt 15 ]]; then
1191		string15=$hostname
1192		upcase_nodename=$string15
1193		locase_nodename=$string15
1194	else
1195		upcase_nodename=$hostname
1196		locase_nodename=$hostname
1197	fi
1198
1199	netbios_nodename="${upcase_nodename}\$"
1200	fqdn=$hostname.$domain
1201	upn=host/${fqdn}@${realm}
1202
1203	grep=/usr/xpg4/bin/grep
1204
1205	object=$(mktemp -q -t kclient-computer-object.XXXXXX)
1206	if [[ -z $object ]]; then
1207		printf "\n$(gettext "Can not create temporary file, exiting").\n
1208" >&2
1209		error_message
1210        fi
1211
1212	grep=/usr/xpg4/bin/grep
1213
1214	modify_existing=false
1215	recreate=false
1216
1217	DomainDnsZones=$(rev_resolve DomainDnsZones.$dom.)
1218	ForestDnsZones=$(rev_resolve ForestDnsZones.$dom.)
1219
1220	getBaseDN "$container" "$dom"
1221
1222	if [[ -n $KDC ]]; then
1223		dc=$KDC
1224	else
1225		getKDCDC
1226	fi
1227
1228	write_ads_krb5conf
1229
1230	printf "$(gettext "Attempting to join '%s' to the '%s' domain").\n\n" $upcase_nodename $realm
1231
1232	kinit $cprinc@$realm
1233	if [[ $? -ne 0 ]]; then
1234		printf "$(gettext "Could not authenticate %s.  Exiting").\n" $cprinc@$realm >&2
1235		error_message
1236	fi
1237
1238	if getForestName
1239	then
1240		printf "\n$(gettext "Forest name found: %s")\n\n" $forest
1241	else
1242		printf "\n$(gettext "Forest name not found, assuming forest is the domain name").\n"
1243	fi
1244
1245	getGC
1246	getSite
1247
1248	if [[ -z $siteName ]]
1249	then
1250    		printf "$(gettext "Site name not found.  Local DCs/GCs will not be discovered").\n\n"
1251	else
1252    		printf "$(gettext "Looking for _local_ KDCs, DCs and global catalog servers (SRV RRs)").\n"
1253		getKDCDC
1254		getGC
1255
1256		write_ads_krb5conf
1257	fi
1258
1259	if [[ ${#GCs} -eq 0 ]]; then
1260		printf "$(gettext "Could not find global catalogs.  Exiting").\n" >&2
1261		error_message
1262	fi
1263
1264	# Check to see if the client is transitioning between domains.
1265	compareDomains $realm
1266
1267	# Here we check domainFunctionality to see which release:
1268	# 0, 1, 2: Windows 2000, 2003 Interim, 2003 respecitively
1269	# 3: Windows 2008
1270	level=0
1271	ldapsearch -R -T -h "$dc" $ldap_args -b "" -s base "" \
1272	 domainControllerFunctionality| grep ^domainControllerFunctionality| \
1273	 read j level
1274	if [[ $? -ne 0 ]]; then
1275		printf "$(gettext "Search for domain functionality failed, exiting").\n" >&2
1276		error_message
1277	fi
1278
1279	if ldapsearch -R -T -h "$dc" $ldap_args -b "$baseDN" \
1280	    -s sub sAMAccountName="$netbios_nodename" dn > /dev/null 2>&1
1281	then
1282		:
1283	else
1284		printf "$(gettext "Search for node failed, exiting").\n" >&2
1285		error_message
1286	fi
1287	ldapsearch -R -T -h "$dc" $ldap_args -b "$baseDN" -s sub \
1288	    sAMAccountName="$netbios_nodename" dn|grep "^dn:"|read j dn
1289
1290	if [[ -z $dn ]]; then
1291		: # modify_existing is already false, which is what we want.
1292	else
1293		printf "$(gettext "Computer account '%s' already exists in the '%s' domain").\n" $upcase_nodename $realm
1294		query "$(gettext "Do you wish to recreate this computer account") ?"
1295		printf "\n"
1296		if [[ $answer == yes ]]; then
1297			recreate=true
1298		else
1299			modify_existing=true
1300		fi
1301	fi
1302
1303	if [[ $modify_existing == false && -n $dn ]]; then
1304		query "$(gettext "Would you like to delete any sub-object found for this computer account") ?"
1305		if [[ $answer == yes ]]; then
1306			printf "$(gettext "Looking to see if the machine account contains other objects")...\n"
1307			ldapsearch -R -T -h "$dc" $ldap_args -b "$dn" -s sub "" dn | while read j sub_dn
1308			do
1309				[[ $j != dn: || -z $sub_dn || $dn == $sub_dn ]] && continue
1310				if $recreate; then
1311					printf "$(gettext "Deleting the following object: %s")\n" ${sub_dn#$dn}
1312					ldapdelete -h "$dc" $ldap_args "$sub_dn" > /dev/null 2>&1
1313					if [[ $? -ne 0 ]]; then
1314						printf "$(gettext "Error in deleting object: %s").\n" ${sub_dn#$dn}
1315					fi
1316				else
1317					printf "$(gettext "The following object will not be deleted"): %s\n" ${sub_dn#$dn}
1318				fi
1319			done
1320		fi
1321
1322		if $recreate; then
1323			ldapdelete -h "$dc" $ldap_args "$dn" > /dev/null 2>&1
1324			if [[ $? -ne 0 ]]; then
1325				printf "$(gettext "Error in deleting object: %s").\n" ${sub_dn#$dn} >&2
1326				error_message
1327			fi
1328		elif $modify_existing; then
1329			: # Nothing to delete
1330		else
1331			printf "$(gettext "A machine account already exists").\n" >&2
1332			error_message
1333		fi
1334	fi
1335
1336	[[ -z $dn ]] && dn="CN=${upcase_nodename},${baseDN}"
1337	if $modify_existing; then
1338		cat > "$object" <<EOF
1339dn: $dn
1340changetype: modify
1341replace: userPrincipalName
1342userPrincipalName: $upn
1343-
1344replace: servicePrincipalName
1345servicePrincipalName: host/${fqdn}
1346-
1347replace: userAccountControl
1348userAccountControl: $((userAccountControlBASE + 32 + 2))
1349-
1350replace: dNSHostname
1351dNSHostname: ${fqdn}
1352EOF
1353
1354		printf "$(gettext "A machine account already exists; updating it").\n"
1355		ldapadd -h "$dc" $ldap_args -f "$object" > /dev/null 2>&1
1356		if [[ $? -ne 0 ]]; then
1357			printf "$(gettext "Failed to modify the AD object via LDAP").\n" >&2
1358			error_message
1359		fi
1360	else
1361		dn="CN=${upcase_nodename},${baseDN}"
1362		cat > "$object" <<EOF
1363dn: $dn
1364objectClass: computer
1365cn: $upcase_nodename
1366sAMAccountName: ${netbios_nodename}
1367userPrincipalName: $upn
1368servicePrincipalName: host/${fqdn}
1369userAccountControl: $((userAccountControlBASE + 32 + 2))
1370dNSHostname: ${fqdn}
1371EOF
1372
1373		printf "$(gettext "Creating the machine account in AD via LDAP").\n\n"
1374
1375		ldapadd -h "$dc" $ldap_args -f "$object" > /dev/null 2>&1
1376		if [[ $? -ne 0 ]]; then
1377			printf "$(gettext "Failed to create the AD object via LDAP").\n" >&2
1378			error_message
1379		fi
1380	fi
1381
1382	# Generate a new password for the new account
1383	MAX_PASS=120
1384        i=0
1385
1386	# first check to see if /dev/random exists to generate a new password
1387	if [[ ! -h /dev/random ]]; then
1388		printf "$(gettext "/dev/random does not exist").\n" >&2
1389		error_message
1390	fi
1391
1392	while ((MAX_PASS > i))
1393	do
1394		# [MS-DISO] A machine password is an ASCII string of randomly
1395		# chosen characters. Each character's ASCII code is between 32
1396		# and 122 inclusive.
1397		c=$(printf "\\$(printf %o $(($(gen_rand) % 91 + 32)))\n")
1398		p="$p$c"
1399		((i+=1))
1400	done
1401
1402	newpw=$p
1403	if [[ ${#newpw} -ne MAX_PASS ]]; then
1404		printf "$(gettext "Password created was of incorrect length").\n" >&2
1405		error_message
1406	fi
1407
1408	# Set the new password
1409	printf "%s" "$newpw" | $KSETPW ${netbios_nodename}@${realm} > /dev/null 2>&1
1410	if [[ $? -ne 0 ]]
1411	then
1412		printf "$(gettext "Failed to set account password").\n" >&2
1413		error_message
1414	fi
1415
1416	# Lookup the new principal's kvno:
1417	ldapsearch -R -T -h "$dc" $ldap_args -b "$baseDN" \
1418		 -s sub cn=$upcase_nodename msDS-KeyVersionNumber| \
1419		grep "^msDS-KeyVersionNumber"|read j kvno
1420	[[ -z $kvno ]] && kvno=1
1421
1422	# Set supported enctypes.  This only works for Longhorn/Vista, so we
1423	# ignore errors here.
1424	userAccountControl=$((userAccountControlBASE + 524288 + 65536))
1425	set -A enctypes --
1426
1427	# Do we have local support for AES?
1428	encrypt -l|grep ^aes|read j minkeysize maxkeysize
1429	val=
1430	if [[ $maxkeysize -eq 256 ]]; then
1431		val=16
1432		enctypes[${#enctypes[@]}]=aes256-cts-hmac-sha1-96
1433	fi
1434	if [[ $minkeysize -eq 128 ]]; then
1435		((val=val+8))
1436		enctypes[${#enctypes[@]}]=aes128-cts-hmac-sha1-96
1437	fi
1438
1439	# RC4 comes next (whether it's better than 1DES or not -- AD prefers it)
1440	if encrypt -l|$grep -q ^arcfour
1441	then
1442		((val=val+4))
1443		enctypes[${#enctypes[@]}]=arcfour-hmac-md5
1444	else
1445		# Use 1DES ONLY if we don't have arcfour
1446		userAccountControl=$((userAccountControl + 2097152))
1447	fi
1448	if encrypt -l | $grep -q ^des
1449	then
1450		((val=val+2))
1451		enctypes[${#enctypes[@]}]=des-cbc-md5
1452	fi
1453
1454	if [[ ${#enctypes[@]} -eq 0 ]]
1455	then
1456		printf "$(gettext "No enctypes are supported").\n"
1457		printf "$(gettext "Please enable arcfour or 1DES, then re-join; see cryptoadm(1M)").\n" >&2
1458		error_message
1459	fi
1460
1461	# If domain crontroller is Longhorn or above then set new supported
1462	# encryption type attributes.
1463	if [[ $level -gt 2 ]]; then
1464		cat > "$object" <<EOF
1465dn: $dn
1466changetype: modify
1467replace: msDS-SupportedEncryptionTypes
1468msDS-SupportedEncryptionTypes: $val
1469EOF
1470		ldapmodify -h "$dc" $ldap_args -f "$object" >/dev/null 2>&1
1471		if [[ $? -ne 0 ]]; then
1472			printf "$(gettext "Warning: Could not set the supported encryption type for computer account").\n"
1473		fi
1474	fi
1475
1476	# We should probably check whether arcfour is available, and if not,
1477	# then set the 1DES only flag, but whatever, it's not likely NOT to be
1478	# available on S10/Nevada!
1479
1480	# Reset userAccountControl
1481	#
1482	#  NORMAL_ACCOUNT (512) | DONT_EXPIRE_PASSWORD (65536) |
1483	#  TRUSTED_FOR_DELEGATION (524288)
1484	#
1485	# and possibly UseDesOnly (2097152) (see above)
1486	#
1487	cat > "$object" <<EOF
1488dn: $dn
1489changetype: modify
1490replace: userAccountControl
1491userAccountControl: $userAccountControl
1492EOF
1493	ldapmodify -h "$dc" $ldap_args -f "$object" >/dev/null 2>&1
1494	if [[ $? -ne 0 ]]; then
1495		printf "$(gettext "ldapmodify failed to modify account attribute").\n" >&2
1496		error_message
1497	fi
1498
1499	# Setup a keytab file
1500	set -A args --
1501	for enctype in "${enctypes[@]}"
1502	do
1503		args[${#args[@]}]=-e
1504		args[${#args[@]}]=$enctype
1505	done
1506
1507	rm $new_keytab > /dev/null 2>&1
1508
1509	cat > "$object" <<EOF
1510dn: $dn
1511changetype: modify
1512add: servicePrincipalName
1513servicePrincipalName: nfs/${fqdn}
1514servicePrincipalName: HTTP/${fqdn}
1515servicePrincipalName: root/${fqdn}
1516servicePrincipalName: cifs/${fqdn}
1517servicePrincipalName: host/${upcase_nodename}
1518EOF
1519	ldapmodify -h "$dc" $ldap_args -f "$object" >/dev/null 2>&1
1520	if [[ $? -ne 0 ]]; then
1521		printf "$(gettext "ldapmodify failed to modify account attribute").\n" >&2
1522		error_message
1523	fi
1524
1525	#
1526	# In Windows, unlike MIT based implementations we salt the keys with
1527	# the UPN, which is based on the host/string15@realm elements, not
1528	# with the individual SPN strings.
1529	#
1530	salt=host/${locase_nodename}.${domain}@${realm}
1531
1532	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" host/${fqdn}@${realm} > /dev/null 2>&1
1533	if [[ $? -ne 0 ]]
1534	then
1535		printf "$(gettext "Failed to set account password").\n" >&2
1536		error_message
1537	fi
1538
1539	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" nfs/${fqdn}@${realm} > /dev/null 2>&1
1540	if [[ $? -ne 0 ]]
1541	then
1542		printf "$(gettext "Failed to set account password").\n" >&2
1543		error_message
1544	fi
1545
1546	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" HTTP/${fqdn}@${realm} > /dev/null 2>&1
1547	if [[ $? -ne 0 ]]
1548	then
1549		printf "$(gettext "Failed to set account password").\n" >&2
1550		error_message
1551	fi
1552
1553	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" root/${fqdn}@${realm} > /dev/null 2>&1
1554	if [[ $? -ne 0 ]]
1555	then
1556		printf "$(gettext "Failed to set account password").\n" >&2
1557		error_message
1558	fi
1559
1560	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" cifs/${fqdn}@${realm} > /dev/null 2>&1
1561	if [[ $? -ne 0 ]]
1562	then
1563		printf "$(gettext "Failed to set account password").\n" >&2
1564		error_message
1565	fi
1566
1567	printf "%s" "$newpw" | $KSETPW -n -s $salt -v $kvno -k "$new_keytab" "${args[@]}" ${netbios_nodename}@${realm} > /dev/null 2>&1
1568	if [[ $? -ne 0 ]]
1569	then
1570		printf "$(gettext "Failed to set account password").\n" >&2
1571		error_message
1572	fi
1573
1574	doKRB5config
1575
1576	addDNSRR $dom
1577
1578	setSMB $dom $dc
1579
1580	printf -- "---------------------------------------------------\n"
1581	printf "$(gettext "Setup COMPLETE").\n\n"
1582
1583	kdestroy -q 1>$TMP_FILE 2>&1
1584	rm -f $TMP_FILE
1585	rm -rf $TMPDIR > /dev/null 2>&1
1586
1587	exit 0
1588}
1589
1590###########################
1591#	Main section	  #
1592###########################
1593#
1594# Set the Kerberos config file and some default strings/files
1595#
1596KRB5_CONFIG_FILE=/etc/krb5/krb5.conf
1597KRB5_KEYTAB_FILE=/etc/krb5/krb5.keytab
1598RESOLV_CONF_FILE=/etc/resolv.conf
1599
1600KLOOKUP=/usr/lib/krb5/klookup;	check_bin $KLOOKUP
1601KSETPW=/usr/lib/krb5/ksetpw;	check_bin $KSETPW
1602KSMB=/usr/lib/krb5/ksmb;	check_bin $KSMB
1603KDYNDNS=/usr/lib/krb5/kdyndns;	check_bin $KDYNDNS
1604
1605dns_lookup=no
1606ask_fqdns=no
1607adddns=no
1608no_keytab=no
1609checkval=""
1610profile=""
1611typeset -u realm
1612typeset -l hostname KDC
1613
1614export TMPDIR="/var/run/kclient"
1615
1616mkdir $TMPDIR > /dev/null 2>&1
1617if [[ $? -ne 0 ]]; then
1618	printf "\n$(gettext "Can not create directory: %s")\n\n" $TMPDIR >&2
1619	exit 1
1620fi
1621
1622TMP_FILE=$(mktemp -q -t kclient-tmpfile.XXXXXX)
1623export KRB5_CONFIG=$(mktemp -q -t kclient-krb5conf.XXXXXX)
1624export KRB5CCNAME=$(mktemp -q -t kclient-krb5ccache.XXXXXX)
1625new_keytab=$(mktemp -q -t kclient-krb5keytab.XXXXXX)
1626if [[ -z $TMP_FILE || -z $KRB5_CONFIG || -z $KRB5CCNAME || -z $new_keytab ]]
1627then
1628	printf "\n$(gettext "Can not create temporary files, exiting").\n\n" >&2
1629	exit 1
1630fi
1631
1632#
1633# If we are interrupted, cleanup after ourselves
1634#
1635trap "exiting 1" HUP INT QUIT TERM
1636
1637if [[ -d /usr/bin ]]; then
1638	if [[ -d /usr/sbin ]]; then
1639		PATH=/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
1640		export PATH
1641	else
1642		printf "\n$(gettext "Directory /usr/sbin not found, exiting").\n" >&2
1643		exit 1
1644	fi
1645else
1646	printf "\n$(gettext "Directory /usr/bin not found, exiting").\n" >&2
1647	exit 1
1648fi
1649
1650printf "\n$(gettext "Starting client setup")\n\n"
1651printf -- "---------------------------------------------------\n"
1652
1653#
1654# Check for uid 0, disallow otherwise
1655#
1656id 1>$TMP_FILE 2>&1
1657if [[ $? -eq 0 ]]; then
1658	if egrep -s "uid=0\(root\)" $TMP_FILE; then
1659		# uid is 0, go ahead ...
1660		:
1661	else
1662		printf "\n$(gettext "Administrative privileges are required to run this script, exiting").\n" >&2
1663		error_message
1664	fi
1665else
1666	cat $TMP_FILE;
1667	printf "\n$(gettext "uid check failed, exiting").\n" >&2
1668	error_message
1669fi
1670
1671uname=$(uname -n)
1672hostname=${uname%%.*}
1673
1674#
1675# Process the command-line arguments (if any)
1676#
1677OPTIND=1
1678while getopts nD:Kp:R:k:a:c:d:f:h:m:s:T: OPTIONS
1679do
1680	case $OPTIONS in
1681	    D) options="$options -D"
1682	       domain_list="$OPTARG"
1683	       ;;
1684	    K) options="$options -K"
1685	       no_keytab=yes
1686	       ;;
1687	    R) options="$options -R"
1688	       realm="$OPTARG"
1689	       checkval="REALM"; check_value $realm
1690	       ;;
1691	    T) options="$options -T"
1692	       type="$OPTARG"
1693	       if [[ $type == ms_ad ]]; then
1694		msad=yes
1695		adddns=yes
1696	       else
1697		non_solaris=yes
1698		no_keytab=yes
1699	       fi
1700	       ;;
1701	    a) options="$options -a"
1702	       ADMIN_PRINC="$OPTARG"
1703	       checkval="ADMIN_PRINC"; check_value $ADMIN_PRINC
1704	       ;;
1705	    c) options="$options -c"
1706	       filepath="$OPTARG"
1707	       ;;
1708	    d) options="$options -d"
1709	       dnsarg="$OPTARG"
1710	       checkval="DNS_OPTIONS"; check_value $dnsarg
1711	       ;;
1712	    f) options="$options -f"
1713	       fqdnlist="$OPTARG"
1714 	       ;;
1715	    h) options="$options -h"
1716	       logical_hn="$OPTARG"
1717	       checkval="LOGICAL_HOSTNAME"; check_value $logical_hn
1718	       ;;
1719	    k) options="$options -k"
1720	       kdc_list="$OPTARG"
1721	       ;;
1722	    m) options="$options -m"
1723	       KDC="$OPTARG"
1724	       checkval="KDC"; check_value $KDC
1725	       ;;
1726	    n) options="$options -n"
1727	       add_nfs=yes
1728	       ;;
1729	    p) options="$options -p"
1730	       profile="$OPTARG"
1731	       read_profile $profile
1732	       ;;
1733	    s) options="$options -s"
1734	       svc_list="$OPTARG"
1735	       SVCs=${svc_list//,/ }
1736 	       ;;
1737	    \?) usage
1738	       ;;
1739	    *) usage
1740	       ;;
1741	esac
1742done
1743
1744#correct argument count after options
1745shift `expr $OPTIND - 1`
1746
1747if [[ -z $options ]]; then
1748	:
1749else
1750	if [[ $# -ne 0 ]]; then
1751		usage
1752	fi
1753fi
1754
1755#
1756# Check to see if we will be a client of a MIT, Heimdal, Shishi, etc.
1757#
1758if [[ -z $options ]]; then
1759	query "$(gettext "Is this a client of a non-Solaris KDC") ?"
1760	non_solaris=$answer
1761	if [[ $non_solaris == yes ]]; then
1762		printf "$(gettext "Which type of KDC is the server"):\n"
1763		printf "\t$(gettext "ms_ad: Microsoft Active Directory")\n"
1764		printf "\t$(gettext "mit: MIT KDC server")\n"
1765		printf "\t$(gettext "heimdal: Heimdal KDC server")\n"
1766		printf "\t$(gettext "shishi: Shishi KDC server")\n"
1767		printf "$(gettext "Enter required KDC type"): "
1768		read kdctype
1769		if [[ $kdctype == ms_ad ]]; then
1770			msad=yes
1771		elif [[ $kdctype == mit || $kdctype == heimdal || \
1772		    $kdctype == shishi ]]; then
1773			no_keytab=yes
1774		else
1775			printf "\n$(gettext "Invalid KDC type option, valid types are ms_ad, mit, heimdal, or shishi, exiting").\n" >&2
1776			error_message
1777		fi
1778	fi
1779fi
1780
1781[[ $msad == yes ]] && join_domain
1782
1783#
1784# Check for /etc/resolv.conf
1785#
1786if [[ -r $RESOLV_CONF_FILE ]]; then
1787	client_machine=`$KLOOKUP`
1788
1789	if [[ $? -ne 0 ]]; then
1790		if [[ $adddns == no ]]; then
1791			printf "\n$(gettext "%s does not have a DNS record and is required for Kerberos setup")\n" $hostname >&2
1792			error_message
1793		fi
1794
1795	else
1796		#
1797		# If client entry already exists then do not recreate it
1798		#
1799		adddns=no
1800
1801		hostname=${client_machine%%.*}
1802		domain=${client_machine#*.}
1803	fi
1804
1805	short_fqdn=${domain#*.*}
1806	short_fqdn=$(echo $short_fqdn | grep "\.")
1807else
1808	#
1809	# /etc/resolv.conf not present, exit ...
1810	#
1811	printf "\n$(gettext "%s does not exist and is required for Kerberos setup")\n" $RESOLV_CONF_FILE >&2
1812	printf "$(gettext "Refer to resolv.conf(4), exiting").\n" >&2
1813	error_message
1814fi
1815
1816check_nss_conf || printf "$(gettext "/etc/nsswitch.conf does not make use of DNS for hosts and/or ipnodes").\n"
1817
1818[[ -n $fqdnlist ]] && verify_fqdnlist "$fqdnlist"
1819
1820if [[ -z $dnsarg && (-z $options || -z $filepath) ]]; then
1821	query "$(gettext "Do you want to use DNS for kerberos lookups") ?"
1822	if [[ $answer == yes ]]; then
1823		printf "\n$(gettext "Valid DNS lookup options are dns_lookup_kdc, dns_lookup_realm,\nand dns_fallback. Refer krb5.conf(4) for further details").\n"
1824		printf "\n$(gettext "Enter required DNS option"): "
1825		read dnsarg
1826		checkval="DNS_OPTIONS"; check_value $dnsarg
1827		set_dns_value $dnsarg
1828	fi
1829else
1830	[[ -z $dnsarg ]] && dnsarg=none
1831	set_dns_value $dnsarg
1832fi
1833
1834if [[ -n $kdc_list ]]; then
1835	if [[ -z $KDC ]]; then
1836		for kdc in $kdc_list; do
1837			break
1838		done
1839		KDC="$kdc"
1840	fi
1841fi
1842
1843if [[ -z $realm ]]; then
1844	printf "$(gettext "Enter the Kerberos realm"): "
1845	read realm
1846	checkval="REALM"; check_value $realm
1847fi
1848if [[ -z $KDC ]]; then
1849	printf "$(gettext "Specify the master KDC hostname for the above realm"): "
1850	read KDC
1851	checkval="KDC"; check_value $KDC
1852fi
1853
1854FKDC=`$KLOOKUP $KDC`
1855
1856#
1857# Ping to see if the kdc is alive !
1858#
1859ping_check $FKDC "KDC"
1860
1861if [[ -z $kdc_list && (-z $options || -z $filepath) ]]; then
1862	query "$(gettext "Do you have any slave KDC(s)") ?"
1863	if [[ $answer == yes ]]; then
1864		printf "$(gettext "Enter a comma-separated list of slave KDC host names"): "
1865		read kdc_list
1866	fi
1867fi
1868
1869[[ -n $kdc_list ]] && verify_kdcs "$kdc_list"
1870
1871#
1872# Check to see if we will have a dynamic presence in the realm
1873#
1874if [[ -z $options ]]; then
1875	query "$(gettext "Will this client need service keys") ?"
1876	if [[ $answer == no ]]; then
1877		no_keytab=yes
1878	fi
1879fi
1880
1881#
1882# Check to see if we are configuring the client to use a logical host name
1883# of a cluster environment
1884#
1885if [[ -z $options ]]; then
1886	query "$(gettext "Is this client a member of a cluster that uses a logical host name") ?"
1887	if [[ $answer == yes ]]; then
1888		printf "$(gettext "Specify the logical hostname of the cluster"): "
1889		read logical_hn
1890		checkval="LOGICAL_HOSTNAME"; check_value $logical_hn
1891		setup_lhn
1892	fi
1893fi
1894
1895if [[ -n $domain_list && (-z $options || -z $filepath) ]]; then
1896	query "$(gettext "Do you have multiple domains/hosts to map to realm %s"
1897) ?" $realm
1898	if [[ $answer == yes ]]; then
1899		printf "$(gettext "Enter a comma-separated list of domain/hosts
1900to map to the default realm"): "
1901		read domain_list
1902	fi
1903fi
1904[[ -n domain_list ]] && domain_list=${domain_list//,/ }
1905
1906#
1907# Start writing up the krb5.conf file, save the existing one
1908# if already present
1909#
1910writeup_krb5_conf
1911
1912#
1913# Is this client going to use krb-nfs?  If so then we need to at least
1914# uncomment the krb5* sec flavors in nfssec.conf.
1915#
1916if [[ -z $options ]]; then
1917	query "$(gettext "Do you plan on doing Kerberized nfs") ?"
1918	add_nfs=$answer
1919fi
1920
1921if [[ $add_nfs == yes ]]; then
1922	modify_nfssec_conf
1923
1924	#
1925	# We also want to enable gss as we now live in a SBD world
1926	#
1927	svcadm enable svc:/network/rpc/gss:default
1928	[[ $? -ne 0 ]] && printf "$(gettext "Warning: could not enable gss service").\n"
1929fi
1930
1931if [[ -z $options ]]; then
1932	query "$(gettext "Do you want to update /etc/pam.conf") ?"
1933	if [[ $answer == yes ]]; then
1934		printf "$(gettext "Enter a list of PAM service names in the following format: service:{first|only|optional}[,..]"): "
1935		read svc_list
1936		SVCs=${svc_list//,/ }
1937	fi
1938fi
1939[[ -n $svc_list ]] && update_pam_conf
1940
1941#
1942# Copy over krb5.conf master copy from filepath
1943#
1944if [[ -z $options || -z $filepath ]]; then
1945	query "$(gettext "Do you want to copy over the master krb5.conf file") ?"
1946	if [[ $answer == yes ]]; then
1947		printf "$(gettext "Enter the pathname of the file to be copied"): "
1948		read filepath
1949	fi
1950fi
1951
1952if [[ -n $filepath && -r $filepath ]]; then
1953	cp $filepath $KRB5_CONFIG
1954	if [[ $? -eq 0 ]]; then
1955		printf "$(gettext "Copied %s to %s").\n" $filepath $KRB5_CONFIG
1956	else
1957		printf "$(gettext "Copy of %s failed, exiting").\n" $filepath >&2
1958		error_message
1959	fi
1960elif [[ -n $filepath ]]; then
1961	printf "\n$(gettext "%s not found, exiting").\n" $filepath >&2
1962	error_message
1963fi
1964
1965doKRB5config
1966
1967#
1968# Populate any service keys needed for the client in the keytab file
1969#
1970if [[ $no_keytab != yes ]]; then
1971	setup_keytab
1972else
1973	printf "\n$(gettext "Note: %s file not created, please refer to verify_ap_req_nofail in krb5.conf(4) for the implications").\n" $KRB5_KEYTAB_FILE
1974	printf "$(gettext "Client will also not be able to host services that use Kerberos").\n"
1975fi
1976
1977printf -- "\n---------------------------------------------------\n"
1978printf "$(gettext "Setup COMPLETE").\n\n"
1979
1980#
1981# If we have configured the client in a cluster we need to remind the user
1982# to propagate the keytab and configuration files to the other members.
1983#
1984if [[ -n $logical_hn ]]; then
1985	printf "\n$(gettext "Note, you will need to securely transfer the /etc/krb5/krb5.keytab and /etc/krb5/krb5.conf files to all the other members of your cluster").\n"
1986fi
1987
1988#
1989# Cleanup.
1990#
1991kdestroy -q 1>$TMP_FILE 2>&1
1992rm -f $TMP_FILE
1993rm -rf $TMPDIR > /dev/null 2>&1
1994exit 0
1995