#
#   File:    usm-user-d.prc
#   Author:  Phil Matthews, Halcyon Inc.
#   Version: 1.18 01/10/03 15:24:07
#
#   Copyright (c) 1993-1998 Halcyon Inc.
#
#   PrimeAlert USM Module Procedures
#

##############################################################################
# Proc:		setInstKeyVal
#		getInstKeyVal
#		clearInstKeyVal
#
# Abstract:	Method to get/set/clear a key-value pair in the specified
#		slice, using the specified instance and value.
#
# Arguments:	slice		Slice in which to store the key val.
#		inst		Instance to use as the key.
#		val		The value.
#		args		Optional subinstance.  If set, we assume the
#				inst is comma-separated and use the arg'th
#				subinstance as a key.  If not set, the entire
#				instance is used as the key.
#
# Returns:	Nothing.
##############################################################################
proc setInstKeyVal { slice inst val args } {

    set idx [ lindex [ lappend args "" ] 0 ]
    if { $idx != "" } {
	lextract [ split $inst "," ] $idx inst
    }
    define $slice $inst $val
}

proc getInstKeyVal { slice inst args } {

    set idx [ lindex [ lappend args "" ] 0 ]
    if { $idx != "" } {
	lextract [ split $inst "," ] $idx inst
    }
    return [ lookup $slice $inst ]
}

proc clearInstKeyVal { slice inst args } {

    set idx [ lindex [ lappend args "" ] 0 ]
    if { $idx != "" } {
	lextract [ split $inst "," ] $idx inst
    }
    undefine $slice $inst
}

##############################################################################
# Proc:		testAndModify
#
# Abstract:	Method to extend the functionality of a 'testAndIncr' node
#		by allowing you to change the value of a node directly to a
#		specified value.  (Should be used only internally or via a
#		shadow op.)
#
# Arguments:	idx		Row index of node.
#		value		Value information - a two-number list:
#				* a value to test against the node's current
#				  value
#				* the new value to set the node to
#
# Returns:	noError if the value was modified OK; inconsistentValue
#		otherwise.
##############################################################################
proc testAndModify { idx value } {

    lextract $value 0 value 1 newval
    if { [ getValue $idx ] == $value } {

#	The testAndIncr node will increment after setting, so account for
#	that here.  (The alternative:  use the _defer arg to this setValue,
#	but that might have undesirable side effects: to wit, other set
#	actions not running when you expect them to.)
#	-------------------------------------------------------------------
	setValue $idx [ expr $newval - 1 ]
	return $value
    } else {
	error "inconsistentValue"
    }
}

##############################################################################
# Proc:		schedUserCfgTrap
#
# Abstract:	Method to schedule a User Config trap.  Invoked upon
#		activation of the 'spinlock' node.
#
# Arguments:	Nothing.
#
# Returns:	Nothing.
##############################################################################
proc schedUserCfgTrap {} {

    ddl print trace "schedUserCfgTrap\n"
    if { [ catch { ilookup internal userConfigInterval } interval ] } {
	define internal userConfigInterval 1800
	set interval 150
    }
    define value traptimer [ registerOneShot $interval sendUserCfgTrap ]
}

proc sendUserCfgTrap {} {

    ddl print trace "sendUserCfgTrap\n"
#   Locate and cache the userConfig trap node, if necessary.
#   --------------------------------------------------------
    if { [ catch { ilookup peer userConfig } userConfig ] } {
	set userConfig [ locate .iso*base.trapInfo.userConfig ]
	define peer userConfig $userConfig
    }

    undefine value traptimer

#   Schedule another trap - to be cancelled if we hear from the trap
#   recipient via a get or set.
#   ----------------------------------------------------------------
    schedUserCfgTrap

#   Construct the value to be sent.
#   -------------------------------
    set varbind [ list [ snmp get snmpEngineID ] [ getRawValue 0 ] ]
    toe_send $userConfig setValue 0 [ list $varbind ]
}


##############################################################################
# Proc:		cxlUserCfgTrap
#
# Abstract:	Method to cancel a scheduled User Config trap.  This is
#		for when a config update arrives before we have sent our
#		cfg trap, rendering the trap unnecessary.
#
# Arguments:	passthru	Value to pass through.
#
# Returns:	The passthru argument.
##############################################################################
proc cxlUserCfgTrap { passthru } {

   ddl print trace "cxlUserCfgTrap: passthru \[$passthru\]\n"
    set traptimer [ ilookup -d "" value traptimer ]
    if { $traptimer != "" } {
	cancelOneShot $traptimer
	undefine value traptimer
    }
    return $passthru
}

##############################################################################
# Proc:		updateSnmpStackByTable
#
# Abstract:	Method to update the SNMP stack with information about the
#		entire USM user table.
#
# Arguments:	None.
#
# Returns:	Nothing.
##############################################################################
proc updateSnmpStackByTable {} {

    foreach rowname [ getRowNames ] {

	set status [ getRowValue usmUserStatus $rowname ]
	updateSnmpStackByRow $rowname $status
    }
}

##############################################################################
# Proc:		updateSnmpStackByRow
#
# Abstract:	Method to update the SNMP stack with information about the
#		specified USM user row.  If the row is unavailable, having
#		just been deleted, enough information can still be deduced
#		from the row name and value to do the update.
#
# Arguments:	rowname		The row name.
#		value		The new value of the row's rowStatus node.
#
# Returns:	Nothing.
##############################################################################
proc updateSnmpStackByRow { rowname value } {

    ddl print trace "updateSnmpStackByRow: rowname \[$rowname\]; value \[$value\]\n"

#   Ugly hack:  if value is 0 (doesNotExist), change to 6 (destroy) so
#   the snmp stack can understand it.
#   ------------------------------------------------------------------
    if { $value == 0 } { set value 6 }

    set splitname [ split $rowname "," ]

    if { [ catch { getRowData $rowname } rowdata ] } {
	snmp set user [ lindex $splitname 0 ] [ lindex $splitname 1 ] \
			{} {} {} {} $value
    } else {
	set rowdata [ concat [ lindex $rowdata 1 ] ]
	snmp set user [ lindex $splitname 0 ] [ lindex $splitname 1 ] \
			[ lindex $rowdata 4 ] [ lindex $rowdata 5 ] \
			[ lindex $rowdata 7 ] [ lindex $rowdata 8 ] \
			$value
    }
}

##############################################################################
# Proc:		updateValue
#
# Abstract:	Method to update the value at the specified row, but only
#		if the value actually exists already.
#
# Arguments:	rowname		The row name.
#		value		The value.
#		args		Optional args (e.g. _defer) - pass thru to
#				setValue, if it is called.
#
# Returns:	Nothing.
##############################################################################
proc updateValue { rowname value args } {

    ddl print trace "updateValue: rowname \[$rowname\]; value \[$value\]; args \[$args\]\n"
    if { ![ catch { getRowIndex $rowname } index ] } {
	ddl print trace "getRowIndex not caught: index \[$index\]\n"
	setValue $index $value $args
    } else { ddl print trace "getRowIndex caught: result \[$index\]\n" }
}

##############################################################################
# Proc:		kleenIfZero
#
# Abstract:	Method to kleen out the user table if the specified value
#		is zero.  (Could be parametrized a little better, but
#		what the hey.)
#
# Arguments:	value		The value.
#
# Returns:	Nothing.
##############################################################################
proc kleenIfZero { value } {

    set value [ lindex $value end ]

    if { $value == 0 } {

	ddl print trace "kleenIfZero: value \[$value\]: KLEENING NOW.\n"

#	DESTROY (rowStatus 6) each row (except for permanent(4) & readOnly(5)).
#	----------------------------------------------------------
	foreach row [ getRowNames ] {
	    if { [ getRowValue usmUserStorageType $row ] < 4 } {
		setRowValue usmUserStatus $row 6
	    }
	}

    } else {
	ddl print trace "kleenIfZero: value \[$value\]: NOT kleening.\n"
    }
}

##############################################################################
# Proc:		usmUserUpdateSubagents
#
# Abstract:	Method to send out the user update trap to subagents, if they
#               are supported
#
# Arguments:	None
#
# Returns:	Nothing.
##############################################################################
proc usmUserUpdateSubagents {} {
    set object [ getSubagentService ]
    if { $object != "" } {
	toe_send $object sendTrapToActiveSubagents userUpdate
    }
}

##############################################################################
# Proc:         createUsmRow
#
# Abstract:     Method to check for existence of a row for specified
#               engine id in the usm user table, and to create the
#               base row if not found.  Assumes that the usm user table
#	        already exists.
#		This is only used by the cfgserver to add new components to
#		its own usm user table to facilitate propagation of private
#		key changes to each component.
#
# Arguments:    engineid        The engine id.
#
# Returns:      Nothing.
##############################################################################
proc createUsmRow { engineid } {

    ddl print trace "createUsmRow: engineid \[$engineid\]\n"

    set userobj [ locate .iso*users ]
    set username [ lookup value superuser ]
    set localize [ toe_send $userobj "lookup userauthsuppl localize($username)" ]

    set rowname $engineid,$username
    set cfgengineid [ getRowValue usmUserEngineID 1 ]

    if { [ catch { getRowIndex $rowname } idx ] } {
        ddl print trace "Adding new usm user row for $rowname\n"

	# First create the row
        set idx [ getRowIndex $rowname -create ]
        updateInstances
        if { [ catch { promoteDefVals $engineid } result ] } {
	    ddl print info "createUsmRow: promoteDefVals result is $result\n"
	}

        # Need to create superuser key change for this engine id by
	# localizing the original global superuser key and then generating
	# the key change wrt the localized cfgserver superuser key
	set origauthkey [ toe_send $userobj "lookup userauthsuppl origauthkey($username)" ]
        set authprot [ getRowValue usmUserAuthProtocol 1 ]
        set cfgauthkey [ getRowValue usmUserAuthKeyChange 1 ]
        set maxlen [ lookup value maxAuthKeyLen ]

	if { $localize == 1 } {
            set authkey [ snmp key $authprot localize $origauthkey $engineid ]
	} else {
	    set authkey $origauthkey
	}

        set authkeychg [ snmp key $authprot createchange $cfgauthkey $authkey $maxlen ]

	ddl print debug "createUsmRow: auth key change is $authkeychg\n"

        setRowValue usmUserSecurityName $rowname "$username"

	# Set up to clone from cfgserver's superuser row
	# (values like usmUserAuthProtocol, usmUserPrivProtocol are cloned,
	# so they need not be set here explicitly).
        set secnameoid [ fullOidOf usmUserSecurityName ]
        set superidx [ join [ list [
            indexStrToOid $cfgengineid +1x:
        ] [
            indexStrToOid $username +
        ] ] "." ]

        setRowValue usmUserCloneFrom $rowname "$secnameoid.$superidx"

	# Set the remaining values
	set authkeychgobj [ locate usmUserAuthKeyChange ]
	toe_send $authkeychgobj "setParameters securityName $username"
        setRowValue usmUserAuthKeyChange $rowname "$authkeychg"
        setRowValue usmUserPublic $rowname "[ getRowValue usmUserPublic 1 ]"
        setRowValue usmUserStorageType $rowname "[ getRowValue usmUserStorageType 1 ]"
        setRowValue usmUserStatus $rowname "[ getRowValue usmUserStatus 1 ]"
	syncSlice data
    }

}

