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