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