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