#!/usr/bin/ksh93 # # Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. # . /lib/svc/share/smf_include.sh . /lib/svc/share/net_include.sh # # Version # # Versioning is maintained in the location_upgrade/version SMF property. This # property was added after the upgrade done when NWAM Phase 1 delivered the # svc:/network/location:default service. # # History: # 1 - NWAM Phase 1 User location # 2 - Highlander DefaultFixed location # NET_LOC_CURRENT_VERSION=2 NET_LOC_DEF_FMRI="svc:/network/location:default" NET_IPTUN_FMRI="svc:/network/iptun:default" # commands CHMOD=/usr/bin/chmod CHOWN=/usr/bin/chown DHCPINFO=/usr/sbin/dhcpinfo DOMAINNAME=/usr/bin/domainname MV=/usr/bin/mv # # The network/ipfilter package may not be installed in some machines. If not # installed, we can ignore IPFilter related tasks here. Check if the # svc:/network/ipfilter:default service exists, which is installed by the # network/ipfilter package. # /usr/bin/svcs $IPFILTER_FMRI >/dev/null 2>/dev/null && IPFILTER_EXISTS=true # # echoes DHCP controlled interfaces separated by commas # # Don't parse the output of ifconfig(1M) because interfaces that haven't # acquired a DHCP lease also have the DHCP flag set. # get_dhcp_interfaces () { # # 1. parse netstat(1M) output for v4 interfaces in BOUND # or INFORMATION state # 2. make a space-separated list of interface names # $NETSTAT -D -f inet | $NAWK ' $2 ~ /BOUND/ { printf "%s ", $1 } $2 ~ /INFORMATION/ { printf "%s ", $1 }' } # # get_dhcpinfo # # echoes the value received through each interface controlled by DHCP; # multiple values are echoed as a space-separated list # # returns: # 0 => property is set # 1 => property is not set # get_dhcpinfo () { code=$1 # Get all interfaces with DHCP control, IFS is " " interfaces=`get_dhcp_interfaces` info="" for intf in $interfaces; do # If the interface is not IFF_UP, then skip it $IFCONFIG $intf | $GREP UP >/dev/null || continue val=`$DHCPINFO -i $intf $code` if [ $? -eq 0 ]; then if [ "$info" = "" ]; then info="$val" else info="$info $val" fi fi done echo $info } # # refresh_svc # # Refreshes the service. # refresh_svc () { $SVCADM refresh $1 } # # start_svc # # Starts the service temporarily. If the service is already enabled, # svcadm(1M) does nothing. # start_svc () { $SVCADM enable -t $1 } # # stop_svc # # Temporarily disables the service. # stop_svc () { $SVCADM disable -t $1 } # # get_svc_fmri # # Returns just the service FMRI when FMRI is given. # svc:/service/name:instance => svc:/service/name # svc:/service/name => svc:/service/name # service/name:instance => service/name # get_svc_fmri () { echo $1 | $NAWK '{ sub(/:[a-zA-Z0-9]+$/,""); print $1}' } # # get_pg # # Extracts the property group portion of a property name. # get_pg () { echo $1 | $NAWK '{ sub(/\/[a-zA-Z0-9\-_]+/,""); print $1}' } # # set_astring_smf_prop # # If the fmri given specifies a service instance, this function will first # check for the presence of the specified property's group on that service # instance. If the group exists, then the property will be set on the # instance. If the group does not exist on the instance, the property will # be set on the service. # set_astring_smf_prop () { PG=`get_pg $2` $SVCPROP -p $PG -Cq $1 if [ $? -eq 0 ]; then # pg exists on instance; set property there FMRI=$1 else # pg does not exist on instance; set property on service FMRI=`get_svc_fmri $1` fi # If the value ($3) is empty, set the property to have the empty value if [ -z "$3" ]; then $SVCCFG -s $FMRI setprop $2 = astring: \(\) else $SVCCFG -s $FMRI setprop $2 = astring: \"$3\" fi return 0 } # # set_addr_smf_prop # # Logic is same as for set_astring_smf_prop(). Syntax is different for the # net_address and host property types; caller is responsible for specifying # the correct type, which should be one of those two. # set_addr_smf_prop() { PG=`get_pg $2` $SVCPROP -p $PG -Cq $1 if [ $? -eq 0 ]; then # pg exists on instance; set property there FMRI=$1 else # pg does not exist on instance; set property on service FMRI=`get_svc_fmri $1` fi $SVCCFG -s $FMRI setprop $2 = $3: "( $4 )" } # # set_hostname_smf_prop # # Logic is same as for set_astring_smf_prop(). # set_hostname_smf_prop () { PG=`get_pg $2` $SVCPROP -p $PG -Cq $1 if [ $? -eq 0 ]; then # pg exists on instance; set property there FMRI=$1 else # pg does not exist on instance; set property on service FMRI=`get_svc_fmri $1` fi $SVCCFG -s $FMRI setprop $2 = hostname: \"$3\" } # # del_smf_prop # del_smf_prop () { $SVCCFG -s $1 delprop $2 2>/dev/null # # If an instance FMRI is given and delprop fails, delete from the # service FMRI. # if [ $? -eq 1 ]; then FMRI=`get_svc_fmri $1` if [ "$FMRI" != "$1" ]; then $SVCCFG -s $FMRI delprop $2 fi fi } # # loc_is_fixed() loc # # Checks the name and returns: # 0 => location is DefaultFixed # 1 => location is not DefaultFixed # loc_is_fixed () { [ "$1" = "DefaultFixed" ] && return 0 return 1 } # # append_prop # # If has an existing value in and no new , then # appends to a netcfg command to clear . If has a # new , then appends to a netcfg command to set to # if non-empty. If is 1, is a multi-valued # property. # append_prop () { prop=$1 multi=$2 loc=$3 nval=$4 file=$5 # get the existing value in the location first if [ $multi -eq 1 ]; then cval=`nwam_get_loc_list_prop "$loc" $prop` else cval=`nwam_get_loc_prop "$loc" $prop` fi if [ -z "$nval" -a -n "$cval" ]; then echo "clear $prop" >> $file elif [ -n "$nval" -a -n "$file" ]; then echo "set $prop=$nval" >> $file fi } # # append_dns \ # # # "set" or "clear" the different DNS properties # append_dns () { loc=$1 file=$2 configsrc=$3 servers=$4 domain=$5 search=$6 options=$7 sortlist=$8 append_prop "dns-nameservice-configsrc" 1 "$loc" "$configsrc" "$file" append_prop "dns-nameservice-servers" 1 "$loc" "$servers" "$file" append_prop "dns-nameservice-domain" 0 "$loc" "$domain" "$file" append_prop "dns-nameservice-search" 1 "$loc" "$search" "$file" append_prop "dns-nameservice-options" 1 "$loc" "$options" "$file" append_prop "dns-nameservice-sortlist" 1 "$loc" "$sortlist" "$file" } # # append_nis_servers # # "set" or "clear" the different NIS properties. "default-domain" is not # included here because this property is shared with LDAP and handled # separately in the calling function. # append_nis_servers () { loc=$1 file=$2 configsrc=$3 servers=$4 append_prop "nis-nameservice-configsrc" 1 "$loc" "$configsrc" "$file" append_prop "nis-nameservice-servers" 1 "$loc" "$servers" "$file" } # # append_ldap_servers # # "set" or "clear" the different LDAP properties. "default-domain" is not # included here because this property is shared with NIS and handled # separately in the calling function. # append_ldap_servers () { loc=$1 file=$2 configsrc=$3 servers=$4 append_prop "ldap-nameservice-configsrc" 1 "$loc" "$configsrc" "$file" append_prop "ldap-nameservice-servers" 1 "$loc" "$servers" "$file" } # # save_fixed_loc # # Saves the current configuration in the given "fixed" location # save_fixed_loc () { loc=$1 LOC_SCRIPT=$VOL_NETCFG_PATH/save_fixed_loc_"$loc" LOC_FILES_DIR=$LOC_PATH/"$loc" # # Write netcfg(1M) commands to set the properties for the given # location into the file at $LOC_SCRIPT. # if [ ! -d "$LOC_FILES_DIR" ]; then /usr/bin/mkdir -p "$LOC_FILES_DIR" fi # get the activation-mode to see if the location already exists or not act=`nwam_get_loc_prop "$loc" activation-mode` if [ -z "$act" ]; then echo "create loc \"$loc\"" > "$LOC_SCRIPT" else echo "select loc \"$loc\"" > "$LOC_SCRIPT" fi append_prop "activation-mode" 0 "$loc" "system" "$LOC_SCRIPT" NAMESERVICES="" NAMESERVICES_FILE="" DNS_CONFIGSRC="" DNS_DOMAIN="" DNS_SERVERS="" DNS_SEARCH="" DNS_SORTLIST="" DNS_OPTIONS="" NIS_CONFIGSRC="" NIS_SERVERS="" LDAP_CONFIGSRC="" LDAP_SERVERS="" DEFAULT_DOMAIN="" # copy /etc/nsswitch.conf file $CP -p /etc/nsswitch.conf "$LOC_FILES_DIR/nsswitch.tmp" $MV -f "$LOC_FILES_DIR/nsswitch.tmp" "$LOC_FILES_DIR/nsswitch.conf" NAMESERVICES_FILE="$LOC_FILES_DIR/nsswitch.conf" # # If the "host" entry of svc:/system/name-service/switch:default has # "dns", then DNS is being used. Gather DNS information from # svc:/network/dns/client. # $SVCPROP -p config/host $NS_SWITCH_FMRI 2>/dev/null | $GREP "dns" >/dev/null if [ $? -eq 0 ] && service_is_enabled $DNS_CLIENT_FMRI ; then save_dns=true get_smf_comma_prop $DNS_CLIENT_FMRI config/domain | \ read DNS_DOMAIN get_smf_comma_prop $DNS_CLIENT_FMRI config/nameserver | \ read DNS_SERVERS get_smf_comma_prop $DNS_CLIENT_FMRI config/search | \ read DNS_SEARCH get_smf_comma_prop $DNS_CLIENT_FMRI config/sortlist | \ read DNS_SORTLIST get_smf_comma_prop $DNS_CLIENT_FMRI config/options | \ read DNS_OPTIONS # save DNS only if nameservers exist if [ -n "$DNS_SERVERS" ]; then NAMESERVICES="dns," DNS_CONFIGSRC="manual" else DNS_DOMAIN="" DNS_SERVERS="" DNS_SEARCH="" DNS_SORTLIST="" DNS_OPTIONS="" fi fi # # Gather NIS info from svc:/network/nis/domain if # svc:/network/nis/client is enabled. # if service_is_enabled $NIS_CLIENT_FMRI ; then save_nis=true get_smf_prop $NIS_DOMAIN_FMRI config/domainname | \ read DEFAULT_DOMAIN get_smf_prop $NIS_DOMAIN_FMRI config/ypservers | \ read yp_servers for serv in $yp_servers; do if is_valid_addr $serv; then addr="$serv," else addr=`$GREP -iw $serv /etc/inet/hosts \ | $NAWK '{ printf "%s,", $1 }'` fi NIS_SERVERS="${NIS_SERVERS}$addr" done # save NIS only if the default-domain is set if [ -n "$DEFAULT_DOMAIN" ]; then NAMESERVICES="${NAMESERVICES}nis," NIS_CONFIGSRC="manual" fi fi # Gather LDAP info if svc:/network/ldap/client:default is enabled. if service_is_enabled $LDAP_CLIENT_FMRI ; then save_ldap=true $CP -p /var/ldap/ldap_client_file "$LOC_FILES_DIR/ldap_client_file.tmp" $CP -p /var/ldap/ldap_client_cred "$LOC_FILES_DIR/ldap_client_cred.tmp" $MV -f "$LOC_FILES_DIR/ldap_client_file.tmp" "$LOC_FILES_DIR/ldap_client_file" $MV -f "$LOC_FILES_DIR/ldap_client_cred.tmp" "$LOC_FILES_DIR/ldap_client_cred" get_smf_prop $NIS_DOMAIN_FMRI config/domainname | \ read DEFAULT_DOMAIN get_smf_comma_prop $LDAP_CLIENT_FMRI config/server_list | \ read LDAP_SERVERS # save LDAP only if both domain and servers exist if [ -n "$LDAP_SERVERS" -a -n "$DEFAULT_DOMAIN" ]; then NAMESERVICES="${NAMESERVICES}ldap," LDAP_CONFIGSRC="manual" fi fi # if $NAMESERVICES is empty, set it to "files" if [ -z "$NAMESERVICES" ]; then NAMESERVICES="files" fi # Now, write netcfg commands for nameservices append_prop "nameservices" 0 "$loc" "$NAMESERVICES" "$LOC_SCRIPT" append_prop "nameservices-config-file" 0 "$loc" "$NAMESERVICES_FILE" \ "$LOC_SCRIPT" # # append_prop() function has the logic to decide whether to "set" or # "clear" the property. We MUST "set" all the nameservice properties # first before "clear"ing the ones that aren't set. This has to be so # because when we "clear" a property, libnwam checks to make sure that # all nameservice properties that are required are set; if not, then # that "clear" fails. For example, if the "nameservices" property # says "nis" is being configured, but previously "dns" was being # configured, then we need to "clear" the dns-nameservice-* # properties. When this "clear" is done, validation will fail because # having "nis" in "nameservices" requires that "default-domain" is # set; but we haven't gotten to that part yet. So, to simplify, we do # all the "set" first and then all the "clear". # if [ "$save_dns" = "true" ]; then append_dns "$loc" "$LOC_SCRIPT" "$DNS_CONFIGSRC" \ "$DNS_SERVERS" "$DNS_DOMAIN" "$DNS_SEARCH" \ "$DNS_OPTIONS" "$DNS_SORTLIST" fi if [ "$save_nis" = "true" ]; then append_nis_servers "$loc" "$LOC_SCRIPT" "$NIS_CONFIGSRC" \ "$NIS_SERVERS" # "default-domain" is not handled in append_nis_servers() append_prop "default-domain" 0 "$loc" "$DEFAULT_DOMAIN" \ "$LOC_SCRIPT" domain_set=true fi if [ "$save_ldap" = "true" ]; then append_ldap_servers "$loc" "$LOC_SCRIPT" "$LDAP_CONFIGSRC" \ "$LDAP_SERVERS" # "default-domain" is not handled in append_ldap_servers() append_prop "default-domain" 0 "$loc" "$DEFAULT_DOMAIN" \ "$LOC_SCRIPT" domain_set=true fi # # Because of the validation reason (see comment above), now do all the # "clear" for nameservices that aren't set. The logic to write the # "clear" is in the append_prop() function; so the steps below look # exactly the same as above where "set" is done. # if [ "$save_dns" != "true" ]; then append_dns "$loc" "$LOC_SCRIPT" "$DNS_CONFIGSRC" \ "$DNS_SERVERS" "$DNS_DOMAIN" "$DNS_SEARCH" \ "$DNS_OPTIONS" "$DNS_SORTLIST" fi if [ "$save_nis" != "true" ]; then append_nis_servers "$loc" "$LOC_SCRIPT" "$NIS_CONFIGSRC" \ "$NIS_SERVERS" # "default-domain" is shared, only clear if it is not set if [ "$domain_set" != "true" ]; then append_prop "default-domain" 0 "$loc" "$DEFAULT_DOMAIN" \ "$LOC_SCRIPT" domain_cleared=true fi fi if [ "$save_ldap" != "true" ]; then append_ldap_servers "$loc" "$LOC_SCRIPT" "$LDAP_CONFIGSRC" \ "$LDAP_SERVERS" # "default-domain" is shared, only "clear" if it is not set # and has not already been cleared if [ "$domain_set" != "true" -a "$domain_cleared" != "true" ]; then append_prop "default-domain" 0 "$loc" "$DEFAULT_DOMAIN" \ "$LOC_SCRIPT" fi fi # Retrieve NFSv4 domain from SMF NFS_DOMAIN="" if service_is_enabled $MAPID_FMRI ; then get_smf_prop $MAPID_FMRI nfs-props/nfsmapid_domain | \ read NFS_DOMAIN fi # empty values are returned as the two-character string "", unset it [ "$NFS_DOMAIN" == "\"\"" ] && NFS_DOMAIN="" append_prop "nfsv4-domain" 0 "$loc" "$NFS_DOMAIN" "$LOC_SCRIPT" # # Do not copy any of these config files to $LOC_FILES_DIR. # When using the DefaultFixed location, users originally edit # the files listed in the man pages for various services. The # following list of files should retain their original names # and locations rather than be copied to the special NWAM # location because users will continue to edit the file listed # in the man page rather than a copy. Failure to keep these # locations consistent could result in users making # configuration changes that never take affect. # ipf_file="" ipf6_file="" ipnat_file="" ippool_file="" ikev1_file="" ikev2_file="" ipsec_pol_file="" # # IPFilter # # No need to check if $IPFILTER_EXISTS here because when it doesn't, # service_is_enabled() will return 1. # # If the firewall policy is "custom", simply copy the # custom_policy_file to $LOC_FILES_DIR. If the firewall policy is # "none", "allow" or "deny", save the value as "/". When # activating this location afterwards, do_sec() will correctly set the # firewall policy and custom_policy_file. Configuration files are # copied to $LOC_FILES_DIR. # if service_is_enabled $IPFILTER_FMRI ; then get_smf_prop $IPFILTER_FMRI firewall_config_default/policy | \ read FIREWALL_POLICY if [ "$FIREWALL_POLICY" = "custom" ]; then get_smf_prop $IPFILTER_FMRI \ firewall_config_default/custom_policy_file | \ read ipf_file else # save value as /none, /allow, or /deny ipf_file="/$FIREWALL_POLICY" fi get_smf_prop $IPFILTER_FMRI config/ipf6_config_file | \ read ipf6_file get_smf_prop $IPFILTER_FMRI config/ipnat_config_file | \ read ipnat_file get_smf_prop $IPFILTER_FMRI config/ippool_config_file | \ read ippool_file fi # IKEv1 if service_is_enabled $IPSEC_IKE_FMRI ; then get_smf_prop $IPSEC_IKE_FMRI config/config_file | \ read ikev1_file fi # IKEv2 if service_is_enabled $IPSEC_IKEV2_FMRI ; then get_smf_prop $IPSEC_IKEV2_FMRI config/config_file | \ read ikev2_file fi # # IPsec - svc:/network/ipsec/policy:default is always enabled. # Save the value of the config/config_file property. Even if # this file doesn't exist in the system, the service (and # ipsecconf(1M) ) can handle non-existent file. # get_smf_prop $IPSEC_POLICY_FMRI config/config_file | read ipsec_pol_file # write the netcfg commands for the above config files append_prop "ipfilter-config-file" 0 "$loc" "$ipf_file" "$LOC_SCRIPT" append_prop "ipfilter-v6-config-file" 0 "$loc" "$ipf6_file" "$LOC_SCRIPT" append_prop "ipnat-config-file" 0 "$loc" "$ipnat_file" "$LOC_SCRIPT" append_prop "ippool-config-file" 0 "$loc" "$ippool_file" "$LOC_SCRIPT" append_prop "ike-config-file" 0 "$loc" "$ikev1_file" "$LOC_SCRIPT" append_prop "ikev2-config-file" 0 "$loc" "$ikev2_file" "$LOC_SCRIPT" append_prop "ipsecpolicy-config-file" 0 "$loc" "$ipsec_pol_file" \ "$LOC_SCRIPT" # And, done! echo "end" >> "$LOC_SCRIPT" # Now, run the script to create the location. if [ `/usr/bin/id -u` = 0 ]; then /usr/bin/su netadm -c "$NETCFG -f \"$LOC_SCRIPT\"" >/dev/null else $NETCFG -f "$LOC_SCRIPT" >/dev/null fi if [ $? -eq 1 ]; then # If script failed, dump the contents into the log $CAT "$LOC_SCRIPT" fi # Cleanup the script $RM -f "$LOC_SCRIPT" } # # copy_default # # Copies /.dfl to / # copy_default () { $CP -p $1/$2.dfl $1/$2.tmp $MV -f $1/$2.tmp $1/$2 } # # trim # # Removes trailing whitespace of the given string # trim () { echo $1 } # # remove_dups # # Removes duplicate entries from the given list # remove_dups () { vals=`echo $1 | $NAWK '{ for (i = 1; i <= NF; i++) { \ if ($i != "" && !a[$i]++) printf("%s ", $i); } print "" }'` # remove trailing whitespace added in printf() above trim "$vals" } # # do_dns # # Installs DNS information in the svc:/network/dns/client SMF service for the # given location. # # Returns 0 on success, 1 on failure # do_dns () { loc=$1 DNS_SERVICE_FMRI=`get_svc_fmri $DNS_CLIENT_FMRI` DNS_CONFIGSRC=`nwam_get_loc_list_prop "$loc" dns-nameservice-configsrc` if [ -z "$DNS_CONFIGSRC" ]; then echo "missing 'dns-nameservice-configsrc' property for '$loc'" return 1 fi for configsrc in $DNS_CONFIGSRC; do case "$configsrc" in 'manual') DNS_MAN_SERVERS=`nwam_get_loc_list_prop "$loc" \ dns-nameservice-servers` if [ -z "$DNS_MAN_SERVERS" ]; then echo "DNS nameserver not set for '$loc'" return 1 fi DNS_MAN_DOMAIN=`nwam_get_loc_prop "$loc" \ dns-nameservice-domain` DNS_MAN_SEARCH=`nwam_get_loc_list_prop "$loc" \ dns-nameservice-search` DNS_MAN_OPTIONS=`nwam_get_loc_list_prop "$loc" \ dns-nameservice-options` sortlist=`nwam_get_loc_list_prop "$loc" \ dns-nameservice-sortlist` # # dns-nameservice-sortlist is stored as # A.B.C.D/W.X.Y.Z in /etc/resolv.conf. The SMF # property takes the value as A.B.C.D/P. # for val in $sortlist do addr=`echo $val | $NAWK 'FS="/" { print $1 }'` mask=`echo $val | $NAWK 'FS="/" { print $2 }'` plen=`netmask2plen $mask` if [ $? -eq 0 -a "$plen" != "0" ]; then MAN_SORT="${MAN_SORT}$addr/$plen " else MAN_SORT="${MAN_SORT}$addr " fi done DNS_MAN_SORTLIST=`trim "$MAN_SORT"` ;; 'dhcp') DNS_DHCP_DOMAIN=`get_dhcpinfo DNSdmain` DNS_DHCP_SERVERS=`get_dhcpinfo DNSserv` # No DNS search info for IPv4 ;; '*') echo "Unrecognized DNS configsrc ${configsrc}; ignoring" ;; esac done # If both domain and search exist, ignore the domain if [ -n "$DNS_MAN_DOMAIN" -a -n "$DNS_MAN_SEARCH" ]; then DNS_MAN_DOMAIN="" fi # user-supplied domain or search overrides the dhcp-supplied domain if [ -n "$DNS_DHCP_DOMAIN" ]; then if [ -n "$DNS_MAN_DOMAIN" -o -n "$DNS_MAN_SEARCH" ]; then DNS_DHCP_DOMAIN="" fi fi # combine and remove duplicates, if any, from nameservers and domain DNS_SERVERS=`remove_dups "$DNS_MAN_SERVERS $DNS_DHCP_SERVERS"` DNS_DOMAIN=`remove_dups "$DNS_MAN_DOMAIN $DNS_DHCP_DOMAIN"` # Retrieve the existing DNS settings from SMF get_smf_prop $DNS_CLIENT_FMRI config/domain | read C_DNS_DOMAIN get_smf_prop $DNS_CLIENT_FMRI config/search | read C_DNS_SEARCH get_smf_prop $DNS_CLIENT_FMRI config/nameserver | read C_DNS_SERVERS get_smf_prop $DNS_CLIENT_FMRI config/sortlist | read C_DNS_SORTLIST get_smf_prop $DNS_CLIENT_FMRI config/options | read C_DNS_OPTIONS # # If a DNS setting to write does not exist and it does in SMF, remove # the SMF property. Write the value to SMF only if it is different # from the value already in the SMF property. Refresh # svc:/network/dns/client only if a change is made. # refresh=false if [ -z "$DNS_DOMAIN" -a -n "$C_DNS_DOMAIN" ]; then del_smf_prop $DNS_CLIENT_FMRI config/domain refresh=true elif [ "$DNS_DOMAIN" != "$C_DNS_DOMAIN" ]; then set_astring_smf_prop $DNS_CLIENT_FMRI config/domain \ "$DNS_DOMAIN" refresh=true fi if [ -z "$DNS_MAN_SEARCH" -a -n "$C_DNS_SEARCH" ]; then del_smf_prop $DNS_CLIENT_FMRI config/search refresh=true elif [ "$DNS_MAN_SEARCH" != "$C_DNS_SEARCH" ]; then # # config/search is multi-valued astring and has to be '"a" "b"' # # As the syntax here has fairly complicated quoting # requirements, we avoid the added complication of passing # it into a function; the set_astring_smf_prop function is # replicated in-line instead. # $SVCPROP -p config -Cq $DNS_CLIENT_FMRI if [ $? -eq 0 ]; then # config pg exists on instance; set property there FMRI=$DNS_CLIENT_FMRI else # pg does not exist on instance; set prop on service FMRI=$DNS_SERVICE_FMRI fi $SVCCFG -s $FMRI 'setprop config/search = astring: (' \ \"${DNS_MAN_SEARCH// /\" \"}\" ')' refresh=true fi if [ -z "$DNS_SERVERS" -a -n "$C_DNS_SERVERS" ]; then del_smf_prop $DNS_CLIENT_FMRI config/nameserver refresh=true elif [ "$DNS_SERVERS" != "$C_DNS_SERVERS" ]; then set_addr_smf_prop $DNS_CLIENT_FMRI config/nameserver \ net_address "$DNS_SERVERS" refresh=true fi if [ -z "$DNS_MAN_SORTLIST" -a -n "$C_DNS_SORTLIST" ]; then del_smf_prop $DNS_CLIENT_FMRI config/sortlist refresh=true elif [ "$DNS_MAN_SORTLIST" != "$C_DNS_SORTLIST" ]; then set_addr_smf_prop $DNS_CLIENT_FMRI config/sortlist \ net_address "$DNS_MAN_SORTLIST" refresh=true fi if [ -z "$DNS_MAN_OPTIONS" -a -n "$C_DNS_OPTIONS" ]; then del_smf_prop $DNS_CLIENT_FMRI config/options refresh=true elif [ "$DNS_MAN_OPTIONS" != "$C_DNS_OPTIONS" ]; then # config/option is single-values astring and has to be "a b c" set_astring_smf_prop $DNS_CLIENT_FMRI config/options \ "$DNS_MAN_OPTIONS" refresh=true fi if [ "$refresh" = "true" ]; then refresh_svc $DNS_CLIENT_FMRI fi start_svc $DNS_CLIENT_FMRI return 0 } # # do_nis # # Installs NIS information in svc:/network/nis/domain and # svc:/network/nis/client SMF services for the given location. # # Returns 0 on success, 1 on failure # do_nis () { loc=$1 NIS_DOMAIN_SERVICE_FMRI=`get_svc_fmri $NIS_DOMAIN_FMRI` NIS_CONFIGSRC=`nwam_get_loc_list_prop "$loc" nis-nameservice-configsrc` if [ -z "$NIS_CONFIGSRC" ]; then echo "missing 'nis-nameservice-configsrc' property for '$loc'" return 1 fi for configsrc in $NIS_CONFIGSRC; do case "$configsrc" in 'manual') NIS_MAN_SERVERS=`nwam_get_loc_list_prop "$loc" \ nis-nameservice-servers` MAN_DEFAULT_DOMAIN=`nwam_get_loc_prop "$loc" \ default-domain` # user-specified default-domain always wins if [ -z "$MAN_DEFAULT_DOMAIN" ]; then echo "'default-domain' not set for '$loc'" return 1 fi ;; 'dhcp') # Use only the first name DHCP_DEFAULT_DOMAIN=`get_dhcpinfo NISdmain | \ $NAWK '{ print $1 }'` NIS_DHCP_SERVERS=`get_dhcpinfo NISservs` ;; '*') echo "Unrecognized NIS configsrc ${configsrc}; ignoring" ;; esac done # If a user has specified a default-domain, use that if [ -n "$MAN_DEFAULT_DOMAIN" ]; then DEFAULT_DOMAIN=$MAN_DEFAULT_DOMAIN else DEFAULT_DOMAIN=$DHCP_DEFAULT_DOMAIN fi if [ -z "$DEFAULT_DOMAIN" ]; then echo "no 'domainname' for NIS for '$loc'" return 1 fi # # If the domains are the same, combine the ypservers list. If they are # different, use the ones that corresponds to the default-domain. # if [ "$MAN_DEFAULT_DOMAIN" = "$DHCP_DEFAULT_DOMAIN" ]; then NIS_SERVERS=`remove_dups "$NIS_MAN_SERVERS $NIS_DHCP_SERVERS"` elif [ "$DEFAULT_DOMAIN" = "$MAN_DEFAULT_DOMAIN" ]; then NIS_SERVERS="$NIS_MAN_SERVERS" elif [ "$DEFAULT_DOMAIN" = "$DHCP_DEFAULT_DOMAIN" ]; then NIS_SERVERS="$NIS_DHCP_SERVERS" fi # Retrieve the existing NIS settings from SMF get_smf_prop $NIS_DOMAIN_FMRI config/domainname | read C_DEF_DOMAIN get_smf_prop $NIS_DOMAIN_FMRI config/ypservers | read C_NIS_SERVERS # # If a NIS setting to configure does not exist and it does in SMF, # remove the SMF property. Write the value to SMF only if it is # different from the value already in the SMF property. Refresh # svc:/network/nis/domain and svc:/network/nis/client only if a change # is made. # refresh=false if [ "$DEFAULT_DOMAIN" != "$C_DEF_DOMAIN" ]; then set_hostname_smf_prop $NIS_DOMAIN_FMRI config/domainname \ "$DEFAULT_DOMAIN" refresh=true fi if [ -z "$NIS_SERVERS" -a -n "$C_NIS_SERVERS" ]; then del_smf_prop $NIS_DOMAIN_FMRI config/ypservers refresh=true elif [ "$NIS_SERVERS" != "$C_NIS_SERVERS" ]; then set_addr_smf_prop $NIS_DOMAIN_FMRI config/ypservers host \ "$NIS_SERVERS" refresh=true fi if [ "$refresh" = "true" ]; then refresh_svc $NIS_DOMAIN_FMRI refresh_svc $NIS_CLIENT_FMRI fi start_svc $NIS_DOMAIN_FMRI start_svc $NIS_CLIENT_FMRI return 0 } # # do_ldap # # Installs LDAP information in svc:/network/ldap/client for the given location. # # Returns 0 on success, 1 on failure # do_ldap () { loc=$1 NIS_DOMAIN_SERVICE_FMRI=`get_svc_fmri $NIS_DOMAIN_FMRI` LDAP_CONFIGSRC=`nwam_get_loc_list_prop "$loc" \ ldap-nameservice-configsrc` if [ -z "$LDAP_CONFIGSRC" ]; then echo "missing 'ldap-nameservice-configsrc' property for '$loc'" return 1 fi for configsrc in $LDAP_CONFIGSRC; do case "$configsrc" in 'manual') LDAP_SERVERS=`nwam_get_loc_list_prop "$loc" \ ldap-nameservice-servers` DEFAULT_DOMAIN=`nwam_get_loc_prop "$loc" default-domain` if [ -z $LDAP_SERVERS -o -z $DEFAULT_DOMAIN ]; then echo "LDAP configuration could not be set "\ "for '$loc'" return 1 fi ;; '*') echo "Invalid LDAP configsrc ${configsrc}; ignoring" ;; esac done # Retrieve the existing LDAP settings from SMF get_smf_prop $NIS_DOMAIN_FMRI config/domainname | read C_DEF_DOMAIN get_smf_prop $LDAP_CLIENT_FMRI config/server_list | read C_LDAP_SERVERS # If the domainname has changed from the existing value in SMF, set it refresh=false if [ "$DEFAULT_DOMAIN" != "$C_DEF_DOMAIN" ]; then set_hostname_smf_prop $NIS_DOMAIN_FMRI config/domainname \ "$DEFAULT_DOMAIN" refresh=true fi if [ -z "$LDAP_SERVERS" -a -n "$C_LDAP_SERVERS" ]; then del_smf_prop $LDAP_CLIENT_FMRI config/server_list refresh=true elif [ "$LDAP_SERVERS" != "$C_LDAP_SERVERS" ]; then set_addr_smf_prop $LDAP_CLIENT_FMRI config/server_list \ host "$LDAP_SERVERS" refresh=true fi if [ "$refresh" = "true" ]; then refresh_svc $NIS_DOMAIN_FMRI refresh_svc $LDAP_CLIENT_FMRI fi start_svc $NIS_DOMAIN_FMRI start_svc $LDAP_CLIENT_FMRI return 0 } # # do_ns # # Installs different nameservices for location # # Returns 0 on success, 1 on failure # do_ns () { loc=$1 # # nsswitch.conf: copy the file given file to /etc/nsswitch.conf, import # it to SMF and refresh svc:/system/name-service/switch:default. # NAMESERVICES_CONFIG_FILE=`nwam_get_loc_prop "$loc" \ nameservices-config-file` if [ -z "$NAMESERVICES_CONFIG_FILE" ]; then echo "missing 'nameservices-config-file' property for '$loc'" return 1 fi # Copy the file to a temporary file and then move it to # /etc/nsswitch.conf. This will ensure that the file has been flused # to disk. $CP -p $NAMESERVICES_CONFIG_FILE /etc/nsswitch.tmp $MV -f /etc/nsswitch.tmp /etc/nsswitch.conf # Also ensure that permissions and owners are correct $CHMOD 0644 /etc/nsswitch.conf $CHOWN root:sys /etc/nsswitch.conf # Import and refresh the switch to the new configuration $NSCFG import -f $NS_SWITCH_FMRI || return 1 # Wait for refresh to complete so that another refresh of this service # doesn't overwrite /etc/nsswitch.conf file $SVCADM refresh -s $NS_SWITCH_FMRI NAMESERVICES=`nwam_get_loc_list_prop "$loc" nameservices` if [ -z "$NAMESERVICES" ]; then echo "missing 'nameservices' property for location '$loc'" return 1 fi dns_configured=false nis_configured=false ldap_configured=false for ns in $NAMESERVICES; do case "$ns" in 'files') # no additional setup needed for files nameservice ;; 'dns') do_dns "$loc" || return 1 dns_configured=true ;; 'nis') do_nis "$loc" || return 1 nis_configured=true ;; 'ldap') do_ldap "$loc" || return 1 ldap_configured=true ;; '*') echo "Unrecognized nameservices value ${ns}; ignoring" ;; esac done # # If a nameservice is not configured, disable the related services. # If it is configured, it has already been refreshed and enabled. # if [ "$dns_configured" = "false" ]; then stop_svc $DNS_CLIENT_FMRI fi if [ "$nis_configured" = "false" ]; then stop_svc $NIS_CLIENT_FMRI # $NIS_DOMAIN_FMRI is also used by LDAP if [ "$ldap_configured" = "false" ]; then stop_svc $NIS_DOMAIN_FMRI # # stopping nis/domain does not temporarily clear out # domainname(1M) and /etc/defaultdomain # $DOMAINNAME -t "" $RM -f /etc/defaultdomain fi fi if [ "$ldap_configured" = "false" ]; then stop_svc $LDAP_CLIENT_FMRI fi # # Restart the autofs service since it is not restarted # automatically when nameservices are refreshed/restarted. # $SVCADM restart $AUTOFS_FMRI return 0 } # # do_sec # # If config properties are set, update the SMF property and refresh the # service. If config properties are not set, delete the SMF property and # stop the service. Exception is svc:/network/ipsec/policy:default which # is always enabled. # # Returns 0 on success, 1 on failure # do_sec () { loc=$1 ikev1_file=`nwam_get_loc_prop "$loc" ike-config-file` ikev2_file=`nwam_get_loc_prop "$loc" ikev2-config-file` pol_file=`nwam_get_loc_prop "$loc" ipsecpolicy-config-file` ipf_file=`nwam_get_loc_prop "$loc" ipfilter-config-file` ipf6_file=`nwam_get_loc_prop "$loc" ipfilter-v6-config-file` ipnat_file=`nwam_get_loc_prop "$loc" ipnat-config-file` ippool_file=`nwam_get_loc_prop "$loc" ippool-config-file` restart_iptun=false # IKEv1 if [ -n "$ikev1_file" -a -e "$ikev1_file" ]; then set_astring_smf_prop $IPSEC_IKE_FMRI config/config_file \ $ikev1_file refresh_svc $IPSEC_IKE_FMRI start_svc $IPSEC_IKE_FMRI else stop_svc $IPSEC_IKE_FMRI fi # IKEv2 if [ -n "$ikev2_file" -a -e "$ikev2_file" ]; then set_astring_smf_prop $IPSEC_IKEV2_FMRI config/config_file \ $ikev2_file refresh_svc $IPSEC_IKEV2_FMRI start_svc $IPSEC_IKEV2_FMRI else stop_svc $IPSEC_IKEV2_FMRI fi # # IPsec - svc:/network/ipsec/policy:default is always enabled. # Set the config/config_file property if it is going to be # different and refresh the service. # get_smf_prop $IPSEC_POLICY_FMRI config/config_file | read cur_pol_file if [ "$pol_file" != "$cur_pol_file" ]; then set_astring_smf_prop $IPSEC_POLICY_FMRI config/config_file \ $pol_file refresh_svc $IPSEC_POLICY_FMRI restart_iptun=true fi # IPFilter - only if the network/ipfilter package is installed if [ -n "$IPFILTER_EXISTS" ]; then refresh_ipf=false if [ -n "$ipf_file" ]; then # change /none, /allow, and /deny to firewall policy if [ "$ipf_file" = "/none" -o "$ipf_file" = "/allow" \ -o "$ipf_file" = "/deny" ]; then policy=`echo "$ipf_file" | \ $NAWK 'FS="/" { print $2 }'` set_astring_smf_prop $IPFILTER_FMRI \ firewall_config_default/policy $policy # no need to clear the custom_policy_file # property, it isn't "custom" else set_astring_smf_prop $IPFILTER_FMRI \ firewall_config_default/policy "custom" set_astring_smf_prop $IPFILTER_FMRI \ firewall_config_default/custom_policy_file \ $ipf_file fi refresh_ipf=true else # change policy to "none", no need to clear the # custom_policy_file property set_astring_smf_prop $IPFILTER_FMRI \ firewall_config_default/policy "none" # IPFilter has to be refreshed to make the changes # effective. Don't set $refresh_ipf as it keeps # IPFilter online rather than disabled. Refresh after # IPFilter is disabled below. fi if [ -n "$ipf6_file" ]; then set_astring_smf_prop $IPFILTER_FMRI \ config/ipf6_config_file $ipf6_file refresh_ipf=true fi if [ -n "$ipnat_file" ]; then set_astring_smf_prop $IPFILTER_FMRI \ config/ipnat_config_file $ipnat_file refresh_ipf=true fi if [ -n "$ippool_file" ]; then set_astring_smf_prop $IPFILTER_FMRI \ config/ippool_config_file $ippool_file refresh_ipf=true fi if [ "$refresh_ipf" = "true" ]; then refresh_svc $IPFILTER_FMRI start_svc $IPFILTER_FMRI restart_iptun=true else # # If the IPFilter service is in maintenance state and # there may be rules in place, then disabing the # IPFilter service below will clear the maintenance # state but will not remove the rules. Explicitly run # the stop method to clear the rules. # if service_is_in_maintenance $IPFILTER_FMRI; then /lib/svc/method/ipfilter stop fi $SVCADM disable -ts $IPFILTER_FMRI refresh_svc $IPFILTER_FMRI fi fi # # If we refreshed and/or enabled an IPsec policy or IPFilter rule, # bring down all tunnels and then bring them up. This ensures that # there is no connection latching in TCP that would result in unwanted # clear-text connections. Additionally for IPFilter, restarting # network/iptun ensures that there are no connections in progress that # might be an outbound stateful rule. In that case, if IPFilter did # not see the initial SYN/SYN-ACK sequence, then the rules would block # all responses from the peer, causing a "hang". Removing the IP # Tunnel interface will ensure that all connections are dead. # if [ "$restart_iptun" = "true" ]; then $SVCADM restart $NET_IPTUN_FMRI fi return 0 } # # do_nfsv4 # # Updates NFSv4 domain for location in SMF # # Returns 0 on success, 1 on failure # do_nfsv4 () { loc=$1 NFSV4_DOMAIN=`nwam_get_loc_prop "$loc" nfsv4-domain` if [ $? -eq 1 ]; then # If no value is retrieved, set it to empty string NFSV4_DOMAIN="" fi # Set the nfs-props/nfsmapid_domain only if we are changing the domain. get_smf_prop $MAPID_FMRI nfs-props/nfsmapid_domain | read CUR_DOMAIN if [ "$NFSV4_DOMAIN" != "$CUR_DOMAIN" ]; then set_astring_smf_prop $MAPID_FMRI nfs-props/nfsmapid_domain \ "$NFSV4_DOMAIN" refresh_svc $MAPID_FMRI fi start_svc $MAPID_FMRI return 0 } # # check_ipsec_config # # Check the config/config_file property of svc:/network/ipsec/policy service. # If a fixed location is active, config/config_file property should not be # empty. delcust it so that the value from the site-profile or manifest is # set. # check_ipsec_config () { # nothing to do for reactive locations loc_is_fixed "$1" || return get_smf_prop $IPSEC_POLICY_FMRI config/config_file | read file [ -z "$file" ] && $SVCCFG -s svc:/network/ipsec/policy \ delcust config/config_file > /dev/null } # # mark_loc_active # # Set the active location in the persistent private_data/active_location # property. If the non-persistent location/location_applied property doesn't # exist (because we are booting), set it. We don't need to refresh because we # always get the composed view of the properties using the -c flag with # svcprop(1). # mark_loc_active () { # Ensure that the config file for IPsec is set correctly check_ipsec_config "$1" set_astring_smf_prop $SMF_FMRI private_data/active_location "$1" || \ return 1 # set location/location_applied if the property doesn't exist yet $SVCPROP -cq -p location/location_applied $SMF_FMRI if [ $? -eq 1 ]; then $SVCCFG -s $SMF_FMRI \ setprop location/location_applied = boolean: true || \ return 1 fi return 0 } # # activate_loc # # Activates the given location # # Returns 0 on success, 1 on failure # activate_loc () { # # If the last location that was active is of type fixed, save the # existing configuration before activating the new location. # $SVCPROP -c -p private_data/active_location $SMF_FMRI 2>/dev/null | \ read cur_loc if loc_is_fixed "$cur_loc"; then # # If we are asked to activate the same fixed location, there # is no need to save and activate it again. In fact, it is # wrong to activate the location because we will overwrite the # existing configuration changes that user may have made. # [ "$1" = "$cur_loc" ] && return 0 save_fixed_loc "$cur_loc" fi loc=$1 echo "activating '$loc' location" # # if we fail to complete any part of the config, # stop activation work and report failure. # do_sec "$loc" && do_ns "$loc" && do_nfsv4 "$loc" && \ mark_loc_active "$loc" && return 0 return 1 } # # Script entry point # # Arguments to net-loc are # method ('start', 'refresh', 'stop', 'upgrade') # # In a shared-IP zone we need this service to be up, but all of the work # it tries to do is irrelevant (and will actually lead to the service # failing if we try to do it), so just bail out. # In the global zone and exclusive-IP zones we proceed. # smf_configure_ip || exit $SMF_EXIT_OK case "$1" in 'start' | 'refresh') # # We need to create the default (NoNet and Automatic) locations, if # they don't already exist. So: first check for the existence of # each, and then run the appropriate netcfg script(s) as needed. # Restart nwamd if a location is created, as it needs to read it in. # LOC_CREATED="false" if ! loc_exists Automatic ; then $NETCFG -f /etc/nwam/loc/create_loc_auto >/dev/null LOC_CREATED="true" fi if ! loc_exists NoNet ; then NONETPATH=/etc/nwam/loc/NoNet NONETFILES="ipf.conf ipf6.conf" for file in $NONETFILES; do copy_default $NONETPATH $file done $NETCFG -f /etc/nwam/loc/create_loc_nonet >/dev/null LOC_CREATED="true" fi if [ "$LOC_CREATED" = "true" ]; then refresh_svc $NP_DEFAULT_FMRI fi # location selection/activation happens below ;; 'stop') # # network/location is stopping. If a fixed location is active, save # the configuration. # $SVCPROP -c -p private_data/active_location $SMF_FMRI 2>/dev/null | \ read active_loc loc_is_fixed "$active_loc" && save_fixed_loc "$active_loc" exit $SMF_EXIT_OK ;; 'upgrade') version=`$SVCPROP -c -p location_upgrade/version $NET_LOC_DEF_FMRI \ 2>/dev/null` # If the property does not exist, add the property group if [ $? -eq 1 ]; then $SVCCFG -s $NET_LOC_DEF_FMRI addpg location_upgrade \ application 2>/dev/null elif [ "$version" == "$NET_LOC_CURRENT_VERSION" ]; then # upgrade has already been done exit $SMF_EXIT_OK fi # # Before saving the exsting configuration in the DefaultFixed # location, export SMF properties to nsswitch.conf as nscfg(1M) may # not have yet run at this point. # $NSCFG export $NS_SWITCH_FMRI save_fixed_loc "DefaultFixed" set_astring_smf_prop $NET_LOC_DEF_FMRI location_upgrade/version \ "$NET_LOC_CURRENT_VERSION" refresh_svc $NET_LOC_DEF_FMRI exit $SMF_EXIT_OK ;; *) echo "Usage: $0 start | refresh | stop | upgrade" exit 1 ;; esac # # If the non-persistent "location" property group does not exist, create it. # It is usually created by nwamd when it selects a location and sets the # location/selected property. But sometimes the network/location's start # method runs before nwamd has a chance to select a location to activate. # $SVCPROP -cq -p location $SMF_FMRI 2>/dev/null [ $? -eq 1 ] && $SVCCFG -s $SMF_FMRI addpg location application P 2>/dev/null # # The fallback location is the location that we activate if cannot activate # the given location. By default, the fallback location is NoNet but this can # be overridden (via the location/fallback property). # $SVCPROP -c -p location/fallback $SMF_FMRI 2>/dev/null | read fallback if [ $? -eq 1 ]; then fallback=NoNet fi # # The location/selected property specifies the location to activate on this # start/refresh of network/location. nwamd writes the location here that it # wants network/location to activate. The private_data/active_location # property persistently specifies the currently active location. When a # "reboot" is done, the 'stop' method is not run, so if a fixed location is # active, it is not saved. This persistent private_data/active_location # property is used to determine if the existing configuration for a fixed # location has to be saved before the selected or fallback location is # activated. # # If location/selected and private_data/active_location properties have the # same location, this means that network/location has been refreshed - either # from the command-line with svcadm(1M) or from nwamd when an interface comes # online. After a location is activated, the value from location/selected is # copied into the private_data/active_location property. # $SVCPROP -c -p location/selected $SMF_FMRI 2>/dev/null | read selected_loc $SVCPROP -c -p private_data/active_location $SMF_FMRI 2>/dev/null | \ read active_loc # # If the selected reactive location does not exist, we fall back to the # fallback location and poke nwamd so it can re-check conditions. For fixed # locations, create it and continue. # if ! loc_exists "$selected_loc" ; then if ! loc_is_fixed "$selected_loc" ; then activate_loc "$fallback" refresh_svc $NP_DEFAULT_FMRI exit $SMF_EXIT_OK fi # If a fixed location does not exist, create it $NETCFG "create loc \"$selected_loc\";set activation-mode=system" \ >/dev/null # We just created the selected fixed location. Continue on. fi # # if location/location_applied is not set, we're booting; and if that's the # case and location/selected is fixed, we can assume that the current system # settings are already correct (the active fixed location would have been # updated with the current settings on shutdown; or this is the first boot # after upgrade and the settings were applied by an install manifest). All we # need to do in this case is set the active property. # $SVCPROP -cq -p location/location_applied $SMF_FMRI if [ $? -eq 1 ] && loc_is_fixed "$selected_loc" ; then mark_loc_active "$selected_loc" exit $SMF_EXIT_OK fi # # location/selected and private_data/active_location are the same and this # location is of fixed type. Don't re-activate the the location as in the # case of reactive locations below. This preserves the configuration changes # the user has made rather than overwriting with the configuration that was # last saved into this fixed location. # [ "$selected_loc" = "$active_loc" ] && loc_is_fixed "$active_loc" && \ exit $SMF_EXIT_OK # # Both location/selected and private_data/active_location are the same and of # reactive type and non-empty, re-activate the selected location. This is the # case of refreshing a reactive location. # if [ "$selected_loc" = "$active_loc" -a -n "$selected_loc" ]; then # re-activate the selected location if ! activate_loc "$selected_loc"; then echo "failed to re-activate '$selected_loc'" activate_loc "$fallback" refresh_svc $NP_DEFAULT_FMRI fi exit $SMF_EXIT_OK fi # # location/selected and private_data/active_location have different values. # Activate the selected location. # if [ -z "$selected_loc" ]; then # location hasn't been selected; default to the fallback location activate_loc "$fallback" else # activate the selected location if ! activate_loc "$selected_loc"; then echo "failed to activate '$selected_loc'" activate_loc "$fallback" refresh_svc $NP_DEFAULT_FMRI fi fi exit $SMF_EXIT_OK