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