kerberos kclient: switch to ksh
[unleashed.git] / usr / src / cmd / krb5 / kadmin / kclient / kclient.sh
blob2d4fcf69c202effa7a1c1015b9fc0397e26a3bdf
1 #!/bin/ksh
3 # CDDL HEADER START
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.
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.
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]
20 # CDDL HEADER END
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>
26 # This script is used to setup the Kerberos client by
27 # supplying information about the Kerberos realm and kdc.
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.
35 function cleanup {
37 kdestroy -q > $TMP_FILE 2>&1
38 rm -r $TMPDIR > /dev/null 2>&1
40 exit $1
42 function exiting {
44 printf "\n$(gettext "Exiting setup, nothing changed").\n\n"
46 cleanup $1
49 function error_message {
51 printf -- "---------------------------------------------------\n" >&2
52 printf "$(gettext "Setup FAILED").\n\n" >&2
54 cleanup 1
57 function check_bin {
59 typeset bin=$1
61 if [[ ! -x $bin ]]; then
62 printf "$(gettext "Could not access/execute %s").\n" $bin >&2
63 error_message
67 function cannot_create {
68 typeset filename="$1"
69 typeset stat="$2"
71 if [[ $stat -ne 0 ]]; then
72 printf "\n$(gettext "Can not create/edit %s, exiting").\n" $filename >&2
73 error_message
77 function update_pam_conf {
78 typeset PAM TPAM service
80 PAM=/etc/pam.conf
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
88 cp $PAM $TPAM >/dev/null 2>&1
90 printf "$(gettext "Configuring %s").\n\n" $PAM
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
102 done
104 cp $TPAM $PAM > /dev/null 2>&1
106 rm $TPAM > /dev/null 2>&1
109 function modify_nfssec_conf {
110 typeset NFSSEC_FILE="/etc/nfssec.conf"
112 if [[ -r $NFSSEC_FILE ]]; then
113 cat $NFSSEC_FILE > $NFSSEC_FILE.sav
114 cannot_create $NFSSEC_FILE.sav $?
117 cat $NFSSEC_FILE > $TMP_FILE
118 cannot_create $TMP_FILE $?
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 $?
126 function call_kadmin {
127 typeset svc="$1"
128 typeset bool1 bool2 bool3 bool4
129 typeset service_princ getprincsubcommand anksubcommand ktaddsubcommand
130 typeset ktremsubcommand
132 for listentry in $fqdnlist; do
134 # Reset conditional vars to 1
135 bool1=1; bool2=1; bool3=1; bool4=1
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"
143 kadmin -c $KRB5CCNAME -q "$getprincsubcommand" 1>$TMP_FILE 2>&1
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=$?
150 if [[ $bool1 -eq 0 || $bool2 -eq 0 ]]; then
151 kadmin -c $KRB5CCNAME -q "$anksubcommand" 1>$TMP_FILE 2>&1
153 egrep -s "$(gettext "add_principal: Principal or policy already exists while creating \"$service_princ@$realm\".")" $TMP_FILE
154 bool3=$?
156 egrep -s "$(gettext "Principal \"$service_princ@$realm\" created.")" $TMP_FILE
157 bool4=$?
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
166 else
167 printf "$(gettext "%s entry already exists in KDC database").\n" $service_princ >&2
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
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
189 done
192 function writeup_krb5_conf {
193 typeset dh
195 printf "\n$(gettext "Setting up %s").\n\n" $KRB5_CONFIG_FILE
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
203 printf "[libdefaults]\n" 1>&3
204 if [[ $no_keytab == yes ]]; then
205 printf "\tverify_ap_req_nofail = false\n" 1>&3
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
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
224 if [[ -n $domain_list ]]; then
225 for dh in $domain_list; do
226 printf "\t$dh = $realm\n" 1>&3
227 done
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
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
245 printf "\t}\n\n" 1>&3
246 else
247 printf "\tdefault_realm = $realm\n\n" 1>&3
250 else
251 printf "\tdefault_realm = $realm\n\n" 1>&3
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
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
266 printf "\t}\n\n" 1>&3
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
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
281 if [[ -n $domain_list ]]; then
282 for dh in $domain_list; do
283 printf "\t$dh = $realm\n" 1>&3
284 done
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
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
298 printf "\t}\n" 1>&3
301 function ask {
302 typeset question=$1
303 typeset default_answer=$2
305 if [[ -z $default_answer ]]; then
306 printf "$question :"
307 else
308 printf "$question [$default_answer]: "
310 read answer
311 test -z "$answer" && answer="$default_answer"
314 function yesno {
315 typeset question="$1"
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")"`
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
334 function query {
335 yesno "$*"
337 if [[ $answer == no ]]; then
338 printf "\t$(gettext "No action performed").\n"
343 function read_profile {
344 typeset param value
345 typeset file="$1"
347 if [[ ! -d $file && -r $file ]]; then
348 while read param value
350 case $param in
351 REALM) if [[ -z $realm ]]; then
352 realm="$value"
353 checkval="REALM"; check_value $realm
356 KDC) if [[ -z $KDC ]]; then
357 KDC="$value"
358 checkval="KDC"; check_value $KDC
361 ADMIN) if [[ -z $ADMIN_PRINC ]]; then
362 ADMIN_PRINC="$value"
363 checkval="ADMIN_PRINC"
364 check_value $ADMIN_PRINC
367 FILEPATH) if [[ -z $filepath ]]; then
368 filepath="$value"
371 NFS) if [[ -z $add_nfs ]]; then
372 if [[ $value == 1 ]]; then
373 add_nfs=yes
374 else
375 add_nfs=no
379 NOKEY) if [[ -z $no_keytab ]]; then
380 if [[ $value == 1 ]]; then
381 no_keytab=yes
382 else
383 no_keytab=no
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
396 LHN) if [[ -z $logical_hn ]]; then
397 logical_hn="$value"
398 checkval="LOGICAL_HOSTNAME"
399 check_value $logical_hn
402 DNSLOOKUP) if [[ -z $dnsarg ]]; then
403 dnsarg="$value"
404 checkval="DNS_OPTIONS"
405 check_value $dnsarg
408 FQDN) if [[ -z $fqdnlist ]]; then
409 fqdnlist="$value"
410 checkval="FQDN"
411 check_value $fqdnlist
412 verify_fqdnlist "$fqdnlist"
415 MSAD) if [[ -z $msad ]]; then
416 if [[ $value == 1 ]]; then
417 msad=yes
418 non_solaris=yes
419 else
420 msad=no
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
432 function ping_check {
433 typeset machine="$1"
434 typeset string="$2"
436 if ping $machine 2 > /dev/null 2>&1; then
438 else
439 printf "\n$(gettext "%s %s is unreachable, exiting").\n" $string $machine >&2
440 error_message
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
450 break
454 function check_value {
455 typeset arg="$1"
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
469 function set_dns_value {
470 typeset -l arg="$1"
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
484 function verify_kdcs {
485 typeset k_list="$1"
486 typeset -l kdc
487 typeset list fqhn f_list
489 kdc_list=$(echo "$k_list" | sed 's/,/ /g')
491 if [[ -z $k_list ]]; then
492 printf "\n$(gettext "At least one KDC should be listed").\n\n" >&2
493 usage
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
502 else
503 printf "\n$(gettext "%s %s is unreachable, no action performed").\n" "KDC" $fkdc >&2
505 f_list="$f_list $fkdc"
507 done
509 fkdc_list="$f_list"
510 kdc_list="$list"
513 function parse_service {
514 typeset service_list=$1
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
525 function verify_fqdnlist {
526 typeset list="$1"
527 typeset -l hostname
528 typeset -i count=1
529 typeset fqdnlist eachfqdn tmpvar fullhost
531 list=$(echo "$list" | tr -d " " | tr -d "\t")
532 hostname=$(uname -n | cut -d"." -f1)
533 fqdnlist=$client_machine
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"
548 ping_check $fullhost $(gettext "System")
549 if [[ $fullhost == $client_machine ]]; then
551 else
552 fqdnlist="$fqdnlist $fullhost"
555 if [[ $list == *,* ]]; then
556 ((count = count + 1))
557 eachfqdn=$(echo "$list" | cut -d"," -f$count)
558 else
559 break
561 done
565 function setup_keytab {
566 typeset cname ask_fqdns current_release
569 # 1. kinit with ADMIN_PRINC
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
578 echo "$ADMIN_PRINC">$TMP_FILE
580 [[ -n $msad ]] && return
581 if egrep -s '\/admin' $TMP_FILE; then
582 # Already in "/admin" format, do nothing
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")
593 printf "$(gettext "Obtaining TGT for %s") ...\n" $ADMIN_PRINC
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
601 klist 1>$TMP_FILE 2>&1
602 if egrep -s "$(gettext "Valid starting")" $TMP_FILE && egrep -s "kadmin/$FKDC@$realm" $TMP_FILE; then
604 else
605 printf "\n$(gettext "kinit of %s failed, exiting").\n" $ADMIN_PRINC >&2
606 error_message
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(4) ?
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
623 else
624 if [[ -z $fqdnlist ]]; then
625 fqdnlist=$client_machine
629 if [[ $add_nfs == yes ]]; then
630 echo; call_kadmin nfs
633 # Add the host entry to the keytab
634 echo; call_kadmin host
638 function setup_lhn {
639 typeset -l logical_hn
641 echo "$logical_hn" > $TMP_FILE
642 if egrep -s '[^.]\.[^.]+$' $TMP_FILE; then
643 # do nothing, logical_hn is in fqdn format
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")
656 client_machine=$logical_hn
658 ping_check $client_machine $(gettext "System")
661 function 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
665 tiple 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
682 function discover_domain {
683 typeset dom DOMs
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`
691 [[ -z ${DOMs[0]} ]] && return 1
693 dom=${DOMs[0]}
695 dom=${dom#*.}
696 dom=${dom% *}
698 domain=$dom
700 return 0
703 function check_nss_hosts_or_ipnodes_config {
704 typeset backend
706 for backend in $1
708 [[ $backend == dns ]] && return 0
709 done
710 return 1
713 function check_nss_conf {
714 typeset i j hosts_config
716 for i in hosts ipnodes
718 grep "^${i}:" /etc/nsswitch.conf|read j hosts_config
719 check_nss_hosts_or_ipnodes_config "$hosts_config" || return 1
720 done
722 return 0
725 function canon_resolve {
726 typeset name ip
728 name=`$KLOOKUP $1 C`
729 [[ -z $name ]] && name=`$KLOOKUP $1 A`
730 [[ -z $name ]] && return
732 ip=`$KLOOKUP $name I`
733 [[ -z $ip ]] && return
734 for i in $ip
736 if ping $i 2 > /dev/null 2>&1; then
737 break
738 else
741 done
743 cname=`$KLOOKUP $ip P`
744 [[ -z $cname ]] && return
746 print -- "$cname"
749 function rev_resolve {
750 typeset name ip
752 ip=`$KLOOKUP $1 I`
754 [[ -z $ip ]] && return
755 name=`$KLOOKUP $ip P`
756 [[ -z $name ]] && return
758 print -- $name
761 # Convert an AD-style domain DN to a DNS domainname
762 function dn2dns {
763 typeset OIFS dname dn comp components
765 dn=$1
766 dname=
768 OIFS="$IFS"
769 IFS=,
770 set -A components -- $1
771 IFS="$OIFS"
773 for comp in "${components[@]}"
775 [[ "$comp" == [dD][cC]=* ]] || continue
776 dname="$dname.${comp#??=}"
777 done
779 print ${dname#.}
782 # Form a base DN from a DNS domainname and container
783 function getBaseDN {
784 if [[ -n "$2" ]]
785 then
786 baseDN="CN=$1,$(dns2dn $2)"
787 else
788 baseDN="$(dns2dn $2)"
792 # Convert a DNS domainname to an AD-style DN for that domain
793 function dns2dn {
794 typeset OIFS dn labels
796 OIFS="$IFS"
797 IFS=.
798 set -A labels -- $1
799 IFS="$OIFS"
802 for label in "${labels[@]}"
804 dn="${dn},DC=$label"
805 done
807 print -- "${dn#,}"
810 function getSRVs {
811 typeset srv port
813 $KLOOKUP $1 S | while read srv port
815 if ping $srv 2 > /dev/null 2>&1; then
816 print -- $srv $port
818 done
821 function getKDC {
822 typeset j
824 set -A KPWs -- $(getSRVs _kpasswd._tcp.$dom.)
825 kpasswd=${KPWs[0]}
827 if [[ -n $siteName ]]
828 then
829 set -A KDCs -- $(getSRVs _kerberos._tcp.$siteName._sites.$dom.)
830 kdc=${KDCs[0]}
831 [[ -n $kdc ]] && return
834 # No site name
835 set -A KDCs -- $(getSRVs _kerberos._tcp.$dom.)
836 kdc=${KDCs[0]}
837 [[ -n $kdc ]] && return
839 # Default
840 set -A KDCs -- $DomainDnsZones 88
841 kdc=$ForestDnsZones
844 function getDC {
845 typeset j
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
854 # No site name
855 set -A DCs -- $(getSRVs _ldap._tcp.dc._msdcs.$dom.)
856 dc=${DCs[0]}
857 [[ -n $dc ]] && return
859 # Default
860 set -A DCs -- $DomainDnsZones 389
861 dc=$DomainDnsZones
864 function write_ads_krb5conf {
865 typeset kdcs
867 printf "\n$(gettext "Setting up %s").\n\n" $KRB5_CONFIG_FILE
869 for i in ${KDCs[@]}
871 [[ $i == +([0-9]) ]] && continue
872 if [[ -n $kdcs ]]
873 then
874 kdcs="$kdcs,$i"
875 else
876 kdcs=$i
878 done
880 $KCONF -f $KRB5_CONFIG -r $realm -k $kdcs -m $KDC -p SET_CHANGE -d .$dom
882 if [[ $? -ne 0 ]]; then
883 printf "\n$(gettext "Can not update %s, exiting").\n" $KRB5_CONFIG >&2
884 error_message
888 function getForestName {
889 ldapsearch -R -T -h $dc $ldap_args \
890 -b "" -s base "" schemaNamingContext| \
891 grep ^schemaNamingContext|read j schemaNamingContext
893 if [[ $? -ne 0 ]]; then
894 printf "$(gettext "Can't find forest").\n" >&2
895 error_message
897 schemaNamingContext=${schemaNamingContext#CN=Schema,CN=Configuration,}
899 [[ -z $schemaNamingContext ]] && return 1
901 forest=
902 while [[ -n $schemaNamingContext ]]
904 schemaNamingContext=${schemaNamingContext#DC=}
905 forest=${forest}.${schemaNamingContext%%,*}
906 [[ "$schemaNamingContext" = *,* ]] || break
907 schemaNamingContext=${schemaNamingContext#*,}
908 done
909 forest=${forest#.}
912 function getGC {
913 typeset j
915 [[ -n $gc ]] && return 0
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
924 # No site name
925 set -A GCs -- $(getSRVs _ldap._tcp.gc._msdcs.$forest.)
926 gc=${GCs[0]}
927 [[ -n $gc ]] && return
929 # Default
930 set -A GCs -- $ForestDnsZones 3268
931 gc=$ForestDnsZones
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.
939 function ipAddr2num {
940 typeset OIFS
941 typeset -ui16 num
943 if [[ "$1" != +([0-9]).+([0-9]).+([0-9]).+([0-9]) ]]
944 then
945 print 0
946 return 0
949 OIFS="$IFS"
950 IFS=.
951 set -- $1
952 IFS="$OIFS"
954 num=$((${1}<<24 | ${2}<<16 | ${3}<<8 | ${4}))
956 print -- $num
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.
964 function num2ipAddr {
965 typeset -ui16 num
966 typeset -ui10 a b c d
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
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.
981 function netmask2length {
982 typeset -ui16 netmask
983 typeset -i len
985 netmask=$1
986 len=32
987 while [[ $((netmask % 2)) -eq 0 ]]
989 netmask=$((netmask>>1))
990 len=$((len - 1))
991 done
992 print $len
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.
1000 function getSubnets {
1001 typeset -ui16 addr netmask
1002 typeset -ui16 classa=16\#ff000000
1003 typeset -ui16 classb=16\#ffff0000
1004 typeset -ui16 classc=16\#ffffff00
1006 ifconfig -a|while read line
1008 addr=0
1009 netmask=0
1010 set -- $line
1011 [[ $1 == inet ]] || continue
1012 while [[ $# -gt 0 ]]
1014 case "$1" in
1015 inet) addr=$(ipAddr2num $2); shift;;
1016 netmask) eval netmask=16\#$2; shift;;
1017 *) :;
1018 esac
1019 shift
1020 done
1022 [[ $addr -eq 0 || $netmask -eq 0 ]] && continue
1023 [[ $((addr & classa)) -eq 16\#7f000000 ]] && continue
1025 print $(num2ipAddr $((addr & netmask)))/$(netmask2length $netmask)
1026 if [ $netmask -gt $classc ]
1027 then
1028 print $(num2ipAddr $((addr & classc)))/$(netmask2length $classc)
1030 if [ $netmask -gt $classb ]
1031 then
1032 print $(num2ipAddr $((addr & classb)))/$(netmask2length $classb)
1034 if [ $netmask -gt $classa ]
1035 then
1036 print $(num2ipAddr $((addr & classa)))/$(netmask2length $classa)
1038 done
1041 function getSite {
1042 typeset subnet siteDN j ldapsrv subnet_dom
1044 eval "[[ -n \"\$siteName\" ]]" && return
1045 for subnet in $(getSubnets)
1047 ldapsearch -R -T -h $dc $ldap_args \
1048 -p 3268 -b "" -s sub cn=$subnet dn |grep ^dn|read j subnetDN
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
1058 [[ -z $siteDN ]] && continue
1060 eval siteName=${siteDN%%,*}
1061 eval siteName=\${siteName#CN=}
1062 return
1063 done
1066 function doKRB5config {
1067 [[ -f $KRB5_CONFIG_FILE ]] && \
1068 cp $KRB5_CONFIG_FILE ${KRB5_CONFIG_FILE}-pre-kclient
1070 [[ -f $KRB5_KEYTAB_FILE ]] && \
1071 cp $KRB5_KEYTAB_FILE ${KRB5_KEYTAB_FILE}-pre-kclient
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
1079 function addDNSRR {
1080 smbFMRI=svc:/network/smb/server:default
1081 ddnsProp=smbd/ddns_enable
1082 enProp=general/enabled
1084 enabled=`svcprop -p $enProp $smbFMRI`
1085 ddns_enable=`svcprop -p $ddnsProp $smbFMRI`
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
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
1097 $KDYNDNS -d $1 > /dev/null 2>&1
1098 if [[ $? -ne 0 ]]; then
1100 # Non-fatal, we should carry-on as clients may resolve to
1101 # different servers and the client could already exist there.
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
1108 function setSMB {
1109 typeset domain=$1
1110 typeset server=$2
1111 smbFMRI=svc:/network/smb/server
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
1119 svcadm restart $smbFMRI > /dev/null 2>&1
1120 if [[ $? -ne 0 ]]; then
1121 printf "$(gettext "Warning: unable to restart %s").\n" $smbFMRI
1125 function compareDomains {
1126 typeset oldDom hspn newDom=$1
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
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
1147 function getKDCDC {
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
1164 function gen_rand {
1165 typeset -u hex
1167 dd if=/dev/random bs=1 count=1 2>/dev/null | od -A n -tx1 | read hex
1169 printf %s $((16#$hex))
1172 function join_domain {
1173 typeset -u upcase_nodename
1174 typeset -l locase_nodename
1175 typeset -L15 string15
1176 typeset netbios_nodename fqdn
1178 container=Computers
1179 ldap_args="-o authzid= -o mech=gssapi"
1180 userAccountControlBASE=4096
1182 if [[ -z $ADMIN_PRINC ]]; then
1183 cprinc=Administrator
1184 else
1185 cprinc=$ADMIN_PRINC
1188 if ! discover_domain; then
1189 printf "$(gettext "Can not find realm") '%s'.\n" $realm >&2
1190 error_message
1193 dom=$domain
1194 realm=$domain
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
1205 netbios_nodename="${upcase_nodename}\$"
1206 fqdn=$hostname.$domain
1207 upn=host/${fqdn}@${realm}
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
1216 modify_existing=false
1217 recreate=false
1219 DomainDnsZones=$(rev_resolve DomainDnsZones.$dom.)
1220 ForestDnsZones=$(rev_resolve ForestDnsZones.$dom.)
1222 getBaseDN "$container" "$dom"
1224 if [[ -n $KDC ]]; then
1225 dc=$KDC
1226 else
1227 getKDCDC
1230 write_ads_krb5conf
1232 printf "$(gettext "Attempting to join '%s' to the '%s' domain").\n\n" $upcase_nodename $realm
1234 kinit $cprinc@$realm
1235 if [[ $? -ne 0 ]]; then
1236 printf "$(gettext "Could not authenticate %s. Exiting").\n" $cprinc@$realm >&2
1237 error_message
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"
1247 getGC
1248 getSite
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
1258 write_ads_krb5conf
1261 if [[ ${#GCs} -eq 0 ]]; then
1262 printf "$(gettext "Could not find global catalogs. Exiting").\n" >&2
1263 error_message
1266 # Check to see if the client is transitioning between domains.
1267 compareDomains $realm
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
1281 if ldapsearch -R -T -h "$dc" $ldap_args -b "$baseDN" \
1282 -s sub sAMAccountName="$netbios_nodename" dn > /dev/null 2>&1
1283 then
1285 else
1286 printf "$(gettext "Search for node failed, exiting").\n" >&2
1287 error_message
1289 ldapsearch -R -T -h "$dc" $ldap_args -b "$baseDN" -s sub \
1290 sAMAccountName="$netbios_nodename" dn|grep "^dn:"|read j dn
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
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
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}
1318 else
1319 printf "$(gettext "The following object will not be deleted"): %s\n" ${sub_dn#$dn}
1321 done
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
1330 elif $modify_existing; then
1331 : # Nothing to delete
1332 else
1333 printf "$(gettext "A machine account already exists").\n" >&2
1334 error_message
1338 [[ -z $dn ]] && dn="CN=${upcase_nodename},${baseDN}"
1339 if $modify_existing; then
1340 cat > "$object" <<EOF
1341 dn: $dn
1342 changetype: modify
1343 replace: userPrincipalName
1344 userPrincipalName: $upn
1346 replace: servicePrincipalName
1347 servicePrincipalName: host/${fqdn}
1349 replace: userAccountControl
1350 userAccountControl: $((userAccountControlBASE + 32 + 2))
1352 replace: dNSHostname
1353 dNSHostname: ${fqdn}
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
1362 else
1363 dn="CN=${upcase_nodename},${baseDN}"
1364 cat > "$object" <<EOF
1365 dn: $dn
1366 objectClass: computer
1367 cn: $upcase_nodename
1368 sAMAccountName: ${netbios_nodename}
1369 userPrincipalName: $upn
1370 servicePrincipalName: host/${fqdn}
1371 userAccountControl: $((userAccountControlBASE + 32 + 2))
1372 dNSHostname: ${fqdn}
1375 printf "$(gettext "Creating the machine account in AD via LDAP").\n\n"
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
1384 # Generate a new password for the new account
1385 MAX_PASS=120
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
1394 while ((MAX_PASS > i))
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
1404 newpw=$p
1405 if [[ ${#newpw} -ne MAX_PASS ]]; then
1406 printf "$(gettext "Password created was of incorrect length").\n" >&2
1407 error_message
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
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
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 --
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
1436 if [[ $minkeysize -eq 128 ]]; then
1437 ((val=val+8))
1438 enctypes[${#enctypes[@]}]=aes128-cts-hmac-sha1-96
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))
1450 if encrypt -l | grep -q ^des
1451 then
1452 ((val=val+2))
1453 enctypes[${#enctypes[@]}]=des-cbc-md5
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
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
1467 dn: $dn
1468 changetype: modify
1469 replace: msDS-SupportedEncryptionTypes
1470 msDS-SupportedEncryptionTypes: $val
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"
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!
1482 # Reset userAccountControl
1484 # NORMAL_ACCOUNT (512) | DONT_EXPIRE_PASSWORD (65536) |
1485 # TRUSTED_FOR_DELEGATION (524288)
1487 # and possibly UseDesOnly (2097152) (see above)
1489 cat > "$object" <<EOF
1490 dn: $dn
1491 changetype: modify
1492 replace: userAccountControl
1493 userAccountControl: $userAccountControl
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
1501 # Setup a keytab file
1502 set -A args --
1503 for enctype in "${enctypes[@]}"
1505 args[${#args[@]}]=-e
1506 args[${#args[@]}]=$enctype
1507 done
1509 rm $new_keytab > /dev/null 2>&1
1511 cat > "$object" <<EOF
1512 dn: $dn
1513 changetype: modify
1514 add: servicePrincipalName
1515 servicePrincipalName: nfs/${fqdn}
1516 servicePrincipalName: HTTP/${fqdn}
1517 servicePrincipalName: root/${fqdn}
1518 servicePrincipalName: cifs/${fqdn}
1519 servicePrincipalName: host/${upcase_nodename}
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
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.
1532 salt=host/${locase_nodename}.${domain}@${realm}
1534 set -A skeys -- host/${fqdn}@${realm} nfs/${fqdn}@${realm} HTTP/${fqdn}@${realm} \
1535 root/${fqdn}@${realm} cifs/${fqdn}@${realm} \
1536 ${netbios_nodename}@${realm} host/${upcase_nodename}@${realm} \
1537 cifs/${upcase_nodename}@${realm}
1539 ks_args="-n -s $salt -v $kvno -k $new_keytab ${args[@]}"
1541 for skey in ${skeys[@]}
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
1549 done
1551 doKRB5config
1553 addDNSRR $dom
1555 setSMB $dom $dc
1557 printf -- "---------------------------------------------------\n"
1558 printf "$(gettext "Setup COMPLETE").\n\n"
1560 kdestroy -q 1>$TMP_FILE 2>&1
1561 rm -f $TMP_FILE
1562 rm -rf $TMPDIR > /dev/null 2>&1
1564 exit 0
1567 ###########################
1568 # Main section #
1569 ###########################
1571 # Set the Kerberos config file and some default strings/files
1573 KRB5_CONFIG_FILE=/etc/krb5/krb5.conf
1574 KRB5_KEYTAB_FILE=/etc/krb5/krb5.keytab
1575 RESOLV_CONF_FILE=/etc/resolv.conf
1577 KLOOKUP=/usr/lib/krb5/klookup; check_bin $KLOOKUP
1578 KSETPW=/usr/lib/krb5/ksetpw; check_bin $KSETPW
1579 KSMB=/usr/lib/krb5/ksmb; check_bin $KSMB
1580 KDYNDNS=/usr/lib/krb5/kdyndns; check_bin $KDYNDNS
1581 KCONF=/usr/lib/krb5/kconf; check_bin $KCONF
1583 dns_lookup=no
1584 ask_fqdns=no
1585 adddns=no
1586 no_keytab=no
1587 checkval=""
1588 profile=""
1589 typeset -u realm
1590 typeset -l hostname KDC
1592 export TMPDIR="/var/run/kclient"
1594 mkdir $TMPDIR > /dev/null 2>&1
1595 if [[ $? -ne 0 ]]; then
1596 printf "\n$(gettext "Can not create directory: %s")\n\n" $TMPDIR >&2
1597 exit 1
1600 TMP_FILE=$(mktemp -q -t kclient-tmpfile.XXXXXX)
1601 export KRB5_CONFIG=$(mktemp -q -t kclient-krb5conf.XXXXXX)
1602 export KRB5CCNAME=$(mktemp -q -t kclient-krb5ccache.XXXXXX)
1603 new_keytab=$(mktemp -q -t kclient-krb5keytab.XXXXXX)
1604 if [[ -z $TMP_FILE || -z $KRB5_CONFIG || -z $KRB5CCNAME || -z $new_keytab ]]
1605 then
1606 printf "\n$(gettext "Can not create temporary files, exiting").\n\n" >&2
1607 exit 1
1611 # If we are interrupted, cleanup after ourselves
1613 trap "exiting 1" HUP INT QUIT TERM
1615 if [[ -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
1623 else
1624 printf "\n$(gettext "Directory /usr/bin not found, exiting").\n" >&2
1625 exit 1
1628 printf "\n$(gettext "Starting client setup")\n\n"
1629 printf -- "---------------------------------------------------\n"
1632 # Check for uid 0, disallow otherwise
1634 id 1>$TMP_FILE 2>&1
1635 if [[ $? -eq 0 ]]; then
1636 if egrep -s "uid=0\(root\)" $TMP_FILE; then
1637 # uid is 0, go ahead ...
1639 else
1640 printf "\n$(gettext "Administrative privileges are required to run this script, exiting").\n" >&2
1641 error_message
1643 else
1644 cat $TMP_FILE;
1645 printf "\n$(gettext "uid check failed, exiting").\n" >&2
1646 error_message
1649 uname=$(uname -n)
1650 hostname=${uname%%.*}
1653 # Process the command-line arguments (if any)
1655 OPTIND=1
1656 while getopts nD:Kp:R:k:a:c:d:f:h:m:s:T: OPTIONS
1658 case $OPTIONS in
1659 D) options="$options -D"
1660 domain_list="$OPTARG"
1662 K) options="$options -K"
1663 no_keytab=yes
1665 R) options="$options -R"
1666 realm="$OPTARG"
1667 checkval="REALM"; check_value $realm
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
1679 a) options="$options -a"
1680 ADMIN_PRINC="$OPTARG"
1681 checkval="ADMIN_PRINC"; check_value $ADMIN_PRINC
1683 c) options="$options -c"
1684 filepath="$OPTARG"
1686 d) options="$options -d"
1687 dnsarg="$OPTARG"
1688 checkval="DNS_OPTIONS"; check_value $dnsarg
1690 f) options="$options -f"
1691 fqdnlist="$OPTARG"
1693 h) options="$options -h"
1694 logical_hn="$OPTARG"
1695 checkval="LOGICAL_HOSTNAME"; check_value $logical_hn
1697 k) options="$options -k"
1698 kdc_list="$OPTARG"
1700 m) options="$options -m"
1701 KDC="$OPTARG"
1702 checkval="KDC"; check_value $KDC
1704 n) options="$options -n"
1705 add_nfs=yes
1707 p) options="$options -p"
1708 profile="$OPTARG"
1709 read_profile $profile
1711 s) options="$options -s"
1712 svc_list="$OPTARG"
1713 SVCs=${svc_list//,/ }
1715 \?) usage
1717 *) usage
1719 esac
1720 done
1722 #correct argument count after options
1723 shift `expr $OPTIND - 1`
1725 if [[ -z $options ]]; then
1727 else
1728 if [[ $# -ne 0 ]]; then
1729 usage
1734 # Check to see if we will be a client of a MIT, Heimdal, Shishi, etc.
1736 if [[ -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
1759 [[ $msad == yes ]] && join_domain
1762 # Check for /etc/resolv.conf
1764 if [[ -r $RESOLV_CONF_FILE ]]; then
1765 client_machine=`$KLOOKUP`
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
1773 else
1775 # If client entry already exists then do not recreate it
1777 adddns=no
1779 hostname=${client_machine%%.*}
1780 domain=${client_machine#*.}
1783 short_fqdn=${domain#*.*}
1784 short_fqdn=$(echo $short_fqdn | grep "\.")
1785 else
1787 # /etc/resolv.conf not present, exit ...
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(4), exiting").\n" >&2
1791 error_message
1794 check_nss_conf || printf "$(gettext "/etc/nsswitch.conf does not make use of DNS for hosts and/or ipnodes").\n"
1796 [[ -n $fqdnlist ]] && verify_fqdnlist "$fqdnlist"
1798 if [[ -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(4) 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
1807 else
1808 [[ -z $dnsarg ]] && dnsarg=none
1809 set_dns_value $dnsarg
1812 if [[ -n $kdc_list ]]; then
1813 if [[ -z $KDC ]]; then
1814 for kdc in $kdc_list; do
1815 break
1816 done
1817 KDC="$kdc"
1821 if [[ -z $realm ]]; then
1822 printf "$(gettext "Enter the Kerberos realm"): "
1823 read realm
1824 checkval="REALM"; check_value $realm
1826 if [[ -z $KDC ]]; then
1827 printf "$(gettext "Specify the master KDC hostname for the above realm"): "
1828 read KDC
1829 checkval="KDC"; check_value $KDC
1832 FKDC=`$KLOOKUP $KDC`
1835 # Ping to see if the kdc is alive !
1837 ping_check $FKDC "KDC"
1839 if [[ -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
1847 [[ -n $kdc_list ]] && verify_kdcs "$kdc_list"
1850 # Check to see if we will have a dynamic presence in the realm
1852 if [[ -z $options ]]; then
1853 query "$(gettext "Will this client need service keys") ?"
1854 if [[ $answer == no ]]; then
1855 no_keytab=yes
1860 # Check to see if we are configuring the client to use a logical host name
1861 # of a cluster environment
1863 if [[ -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
1873 if [[ -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
1878 to map to the default realm"): "
1879 read domain_list
1882 [[ -n domain_list ]] && domain_list=${domain_list//,/ }
1885 # Start writing up the krb5.conf file, save the existing one
1886 # if already present
1888 writeup_krb5_conf
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.
1894 if [[ -z $options ]]; then
1895 query "$(gettext "Do you plan on doing Kerberized nfs") ?"
1896 add_nfs=$answer
1899 if [[ $add_nfs == yes ]]; then
1900 modify_nfssec_conf
1903 # We also want to enable gss as we now live in a SBD world
1905 svcadm enable svc:/network/rpc/gss:default
1906 [[ $? -ne 0 ]] && printf "$(gettext "Warning: could not enable gss service").\n"
1909 if [[ -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//,/ }
1917 [[ -n $svc_list ]] && update_pam_conf
1920 # Copy over krb5.conf master copy from filepath
1922 if [[ -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
1930 if [[ -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
1938 elif [[ -n $filepath ]]; then
1939 printf "\n$(gettext "%s not found, exiting").\n" $filepath >&2
1940 error_message
1943 doKRB5config
1946 # Populate any service keys needed for the client in the keytab file
1948 if [[ $no_keytab != yes ]]; then
1949 setup_keytab
1950 else
1951 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
1952 printf "$(gettext "Client will also not be able to host services that use Kerberos").\n"
1955 printf -- "\n---------------------------------------------------\n"
1956 printf "$(gettext "Setup COMPLETE").\n\n"
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.
1962 if [[ -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"
1967 # Cleanup.
1969 kdestroy -q 1>$TMP_FILE 2>&1
1970 rm -f $TMP_FILE
1971 rm -rf $TMPDIR > /dev/null 2>&1
1972 exit 0