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