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