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