#!/usr/bin/perl
# $Header: /auto/homes/pb22/pl/RCS/amtool.pl,v 1.17 2015/11/27 11:16:50 pb22 Exp $
# From http://sourceforge.net/projects/amttool-tng/ with cl.cam.ac.uk tweaks
## -----------------------------------------------------------------------
##   This utility can talk to Intel(r) AMT(r) managed machines.
##
##   Copyright 2007 Gerd Hoffmann <kraxel@redhat.com>
##   Copyright 2010-2012 Stanislav V. Zankov
##
##   This program is free software; you can redistribute it and/or modify
##   it under the terms of the GNU General Public License as published by
##   the Free Software Foundation; either version 2 of the License, or
##   (at your option) any later version.
##
##   This program is distributed in the hope that it will be useful,
##   but WITHOUT ANY WARRANTY; without even the implied warranty of
##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##   GNU General Public License for more details.
##
##   You should have received a copy of the GNU General Public License along
##   with this program; if not, write to the Free Software Foundation, Inc.,
##   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##
## -----------------------------------------------------------------------

sub soap_fault;
sub soap_debug;

use strict;
use warnings;
use Time::Zone;
use Digest::MD5 qw(md5_base64);
use MIME::Base64;
use Math::BigInt;

use Carp();
$SIG{__DIE__} = \&Carp::confess;

my $param_force = my $param_quiet = my $param_hex =
my $param_num =  my $use_header  = 0;
my $amt_debug = (defined($ENV{'AMT_DEBUG'}) ? 1 : 0);
my ( $amt_cmnd, $amt_version );

my $schemabase = "http://schemas.intel.com/platform/client";
my $AAconf = "/etc/AMT-AA.conf";

my $amt_host = &bmc_name(shift);
my $amt_port = "16992";

while( defined($amt_host)  &&  $amt_host =~ m/^-/) {
    $param_force = 1 if ($amt_host =~ m/^-(-force|f)$/i);
    $param_quiet = 1 if ($amt_host =~ m/^-(-quiet|q)$/i);
    $param_hex = 1   if ($amt_host =~ m/^-(-hex|x)$/i);
    $param_num = 1   if ($amt_host =~ m/^-(-num(eric)?|n)$/i);
    $amt_debug |= 1   if ($amt_host =~ m/^-(-debug|d)$/i);
    $amt_debug |= 2  if ($amt_host =~ m/^-d(-debug|d)$/i);

    $amt_host = shift;
}

$main::amt_user = ( defined($ENV{'AMT_USER'}) ? $ENV{'AMT_USER'} : "admin" );
$main::amt_pass = $ENV{'AMT_PASSWORD'};
$main::tz = ( defined($ENV{'AMT_TZO'}) ? $ENV{'AMT_TZO'} : (tz_local_offset()));

# cl.cam.ac.uk: use data from dot files.
sub file_val {
	my ($file) = @_;
	my @files = ( "/etc/$file" );
	unshift @files, "$ENV{'HOME'}/$file" if $ENV{'HOME'};
	push @files, "/root/$file" if $main::raised_priv;
	foreach my $file (@files) {
		if (open(PWFILE, $file)) {
			my $val = <PWFILE>;
			close(PWFILE);
			chomp $val;
			print STDERR "# $0: found value in $file (@files)\n" if $amt_debug;
			return $val if $val;
		}
	}
	return undef;
}
$main::raised_priv = ($ENV{'SUDO_USER'}) ? " $ENV{'SUDO_USER'} as root" : "";	# running under sudo, so restrict what can be done.
$main::amt_user = $ENV{'AMT_USER'};
$main::amt_pass = file_val(".amtpw")   unless $main::amt_pass;
$main::amt_user = file_val(".amtuser") unless $main::amt_user;
if (! $main::amt_user && ! $main::amt_pass && $ENV{'AAPRINCIPAL'}) {
	my $user = $ENV{'AAPRINCIPAL'};
	print STDERR "# $0: look for $user in $AAconf ...\n" if $amt_debug;
	if (open(CONF, $AAconf)) {
		while (<CONF>) {
			chomp;
			next if /^#/;
			if (/^$user\s+(\S+)\s*$/) {
				$main::amt_user = $user;
				$main::amt_pass = $1;
			}
		}
		close(CONF);
		print STDERR "# $0: found PW for $user in $AAconf\n" if $main::amt_user && $amt_debug;
	}
}
$main::amt_user = "admin" unless $main::amt_user;

our $last_alaarm = "<unset>";
$amt_cmnd = shift;

# Under U12.04 install libsoap-lite-perl
eval "use SOAP::Lite autotype => 0, on_fault => \\&soap_fault".
    ($amt_debug ? ", +trace => [ transport => \\&soap_debug ], readable => 1" : "")."; 1"
    or die;


#############################################################################
# data

my @ps = ("S0 (running)", "S1", "S2", "S3 (suspend)", "S4 (hibernate)", "S5 (soft-off)", "S4/S5", "G3 Off", "S1/S2/S3",
	  "G1 Sleep", "S5 by PwrButt", "Legacy ON", "Legacy OFF", "Unknown" );
my %rcc = (
	"reset"      => 0x10,
	"powerup"    => 0x11,
	"powerdown"  => 0x12,
	"powercycle" => 0x13,
	"setbootopt" => 0x21,
);

my %rccs = (		# SpecialCommand
	"nop"        => 0x00,
	"pxe"	     => 0x01,
	"hd"         => 0x02,
	"hdsafe"     => 0x03,
	"diag"       => 0x04,
	"cd"         => 0x05,
);
my %rccsp = (		# SpecialCommandParameter
	"iderflop"   => 0x01,
	"idercd"     => 0x101,
	"reflash"    => 0x04,
	"biossetup"  => 0x08,
	"biospause"  => 0x10,
);
my %rccp_oem = (		# OEMParameters
	"sol"       => 0x01,
);
my %rcbo = (		# -BootOptions-
	"lckpwr"  => 0x0002,	# LockPowerButton
	"lckrst"  => 0x0004,	# LockResetButton
	"lckslp"  => 0x0040,	# LockSleepButton
	"lckkbd"  => 0x0020,	# LockKeyboard
	"pwdbps"  => 0x0800,	# UserPasswordBypass
	"evnt"    => 0x1000,	# ForceProgressEvents
	"vdef"    => 0x0000,	# VerbosityDefault
	"vqut"    => 0x2000,	# VerbosityQuiet
	"vvrb"    => 0x4000,	# VerbosityVerbose
	"vbln"    => 0x6000,	# VerbosityBlank
	"cfgres"  => 0x8000,	# ConfigurationDataReset
);

# incomplete list
my %pt_status_desc = (
	0x0  =>  "success",
	0x1  =>  "internal error",
	0x3  =>  "invalid pt_mode",
	0xc  =>  "invalid name",
	0xf  =>  "invalid byte_count",
	0x10  =>  "not permitted",
	0x17  =>  "max limit_reached",
	0x18  =>  "invalid auth_type",
	0x1a  =>  "invalid dhcp_mode",
	0x1b  =>  "invalid ip_address",
	0x1c  =>  "invalid domain_name",
	0x20  =>  "invalid provisioning_state",
	0x22  =>  "invalid time",
	0x23  =>  "invalid index",
	0x24  =>  "invalid parameter",
	0x25  =>  "invalid netmask",
	0x26  =>  "flash write_limit_exceeded",
	0x800  =>  "network if_error_base",
	0x801  =>  "unsupported oem_number",
	0x802  =>  "unsupported boot_option",
	0x803  =>  "invalid command",
	0x804  =>  "invalid special_command",
	0x805  =>  "invalid handle",
	0x806  =>  "invalid password",
	0x807  =>  "invalid realm",
	0x808  =>  "storage acl_entry_in_use",
	0x809  =>  "data missing",
	0x80a  =>  "duplicate",
	0x80b  =>  "eventlog frozen",
	0x80c  =>  "pki missing_keys",
	0x80d  =>  "pki generating_keys",
	0x80e  =>  "invalid key",
	0x80f  =>  "invalid cert",
	0x810  =>  "cert key_not_match",
	0x811  =>  "max kerb_domain_reached",
	0x812  =>  "unsupported",
	0x813  =>  "invalid priority",
	0x814  =>  "not found",
	0x815  =>  "invalid credentials",
	0x816  =>  "invalid passphrase",
	0x818  =>  "no association",
);

my %pt_status = (
    "PT_STATUS_SUCCESS"                    => 0x0,
    "PT_STATUS_INTERNAL_ERROR"             => 0x1,
    "PT_STATUS_NOT_READY"                  => 0x2,
    "PT_STATUS_INVALID_PT_MODE"            => 0x3,
    "PT_STATUS_INVALID_NAME"               => 0xC,
    "PT_STATUS_INVALID_BYTE_COUNT"         => 0xF,
    "PT_STATUS_NOT_PERMITTED"              => 0x10,
    "PT_STATUS_MAX_LIMIT_REACHED"          => 0x17,
    "PT_STATUS_INVALID_AUTH_TYPE"          => 0x18,
    "PT_STATUS_AUTHENTICATION_FAILURE"     => 0x19,
    "PT_STATUS_INVALID_DHCP_MODE"          => 0x1A,
    "PT_STATUS_INVALID_IP_ADDRESS"         => 0x1B,
    "PT_STATUS_INVALID_DOMAIN_NAME"        => 0x1C,
    "PT_STATUS_INVALID_PROVISIONING_STATE" => 0x20,
    "PT_STATUS_INVALID_TIME"               => 0x22,
    "PT_STATUS_INVALID_INDEX"              => 0x23,
    "PT_STATUS_INVALID_PARAMETER"          => 0x24,
    "PT_STATUS_INVALID_NETMASK"            => 0x25,
    "PT_STATUS_FLASH_WRITE_LIMIT_EXCEEDED" => 0x26,
    "PT_STATUS_DISABLED_BY_POLICY"         => 0x400,
    "PT_STATUS_NETWORK_IF_ERROR_BASE"      => 0x800,
    "PT_STATUS_UNSUPPORTED_OEM_NUMBER"     => 0x801,
    "PT_STATUS_UNSUPPORTED_BOOT_OPTION"    => 0x802,
    "PT_STATUS_INVALID_COMMAND"            => 0x803,
    "PT_STATUS_INVALID_SPECIAL_COMMAND"    => 0x804,
    "PT_STATUS_INVALID_HANDLE"             => 0x805,
    "PT_STATUS_INVALID_PASSWORD"           => 0x806,
    "PT_STATUS_INVALID_REALM"              => 0x807,
    "PT_STATUS_STORAGE_ACL_ENTRY_IN_USE"   => 0x808,
    "PT_STATUS_DATA_MISSING"               => 0x809,
    "PT_STATUS_DUPLICATE"                  => 0x80A,
    "PT_STATUS_EVENTLOG_FROZEN"            => 0x80B,
    "PT_STATUS_PKI_MISSING_KEYS"           => 0x80C,
    "PT_STATUS_PKI_GENERATING_KEYS"        => 0x80D,
    "PT_STATUS_INVALID_KEY"                => 0x80E,
    "PT_STATUS_INVALID_CERT"               => 0x80F,
    "PT_STATUS_CERT_KEY_NOT_MATCH"         => 0x810,
    "PT_STATUS_MAX_KERB_DOMAIN_REACHED"    => 0x811,
    "PT_STATUS_UNSUPPORTED"                => 0x812,
    "PT_STATUS_INVALID_PRIORITY"           => 0x813,
    "PT_STATUS_NOT_FOUND"                  => 0x814,
    "PT_STATUS_INVALID_CREDENTIALS"        => 0x815,
    "PT_STATUS_INVALID_PASSPHRASE"         => 0x816,
    "PT_STATUS_NO_ASSOCIATION"             => 0x818,
    "PT_STATUS_AUDIT_FAIL"                 => 0x81B,
    "PT_STATUS_BLOCKING_COMPONENT"         => 0x81C
);

my %provis_state = (
    "ProvisioningStatePre"  => 'Pre Provisioning (Factory Mode)',
    "ProvisioningStateIn"   => 'In Provisioning (Setup Mode)',
    "ProvisioningStatePost" => 'Post Provisioning (Operational Mode)'
);

my %pwd_model = (
    "PasswordModelCoupled"      => 'Coupled (the password of the network and the local interfaces are identical)',
    "PasswordModelSeperate"     => 'Separate (the password of the network and the local interfaces are separate)',
    "PasswordModelSeperateHash" => 'Separate-Hash password model'
);

my %me_capabilities = (
    0 => "CommonServices",
    1 => "AMT",
    2 => "SAL",
    3 => "Reserved3",
    4 => "Reserved4"
);

my %net_dhcp_modes = (
    0 => "Invalid",
    1 => "Disabled",
    2 => "Enabled"
);

my %net_dhcp_modes_code = (
    "DhcpModeInvalid"  => 0,
    "DhcpModeDisabled" => 1,
    "DhcpModeEnabled"  => 2
);

my %acl_access_permission = (
    "local"   => "LocalAccessPermission",
    "network" => "NetworkAccessPermission",
    "any"     => "AnyAccessPermission"
);

my %user_acl_realm = (
    0  =>  "Invalid",
    1  =>  "RCS",
    2  =>  "Redirection",
    3  =>  "PTAdministration",
    4  =>  "HardwareAsset",
    5  =>  "RemoteControl",
    6  =>  "Storage",
    7  =>  "EventManager",
    8  =>  "StorageAdmin",
    9  =>  "AgentPresenceLocal",
    10 =>  "AgentPresenceRemote",
    11 =>  "CircuitBreaker",
    12 =>  "NetworkTime",
    13 =>  "GeneralInfo",
    14 =>  "FirmwareUpdate",
    15 =>  "EIT",
    16 =>  "LocalUN",
    17 =>  "EndpointAccessControl",
    18 =>  "EndpointAccessControlAdmin",
    19 =>  "EventLogReader",
    20 =>  "SecurityAuditLog",
    21 =>  "UserAccessControl",
    22 =>  "Reserved1",
    23 =>  "Reserved2"
);
my @user_acl_realm = (
    "Invalid",
    "RCS",
    "Redirection",
    "PTAdministration",
    "HardwareAsset",
    "RemoteControl",
    "Storage",
    "EventManager",
    "StorageAdmin",
    "AgentPresenceLocal",
    "AgentPresenceRemote",
    "CircuitBreaker",
    "NetworkTime",
    "GeneralInfo",
    "FirmwareUpdate",
    "EIT",
    "LocalUN",
    "EndpointAccessControl",
    "EndpointAccessControlAdmin",
    "EventLogReader",
    "SecurityAuditLog",
    "UserAccessControl",
    "Reserved1",
    "Reserved2"
);
my %user_acl_realm_codes = (
    "rcs"                        => 1,
    "redirection"                => 2,
    "ptadministration"           => 3,
    "hardwareasset"              => 4,
    "remotecontrol"              => 5,
    "storage"                    => 6,
    "eventmanager"               => 7,
    "storageadmin"               => 8,
    "agentpresencelocal"         => 9,
    "agentpresenceremote"        => 10,
    "circuitbreaker"             => 11,
    "networktime"                => 12,
    "generalinfo"                => 13,
    "firmwareupdate"             => 14,
    "eit"                        => 15,
    "localun"                    => 16,
    "endpointaccesscontrol"      => 17,
    "endpointaccesscontroladmin" => 18,
    "eventlogreader"             => 19,
    "securityauditlog"           => 20,
    "useraccesscontrol"          => 21,
    "reserved1"                  => 22,
    "reserved2"                  => 23
);

my %hwasset_type_names = (
    1  => "All",
    2  => "BIOS",
    3  => "ComputerSystem",
    4  => "Baseboard",
    5  => "Processor",
    6  => "MemoryModule",
    7  => "FRU",
    8  => "MediaDevice",
    9  => "PortableBattery",
    10 => "VproVerificationTable",
    11 => "AmtInformation"
);

my %hwasset_type_codes = (
    "bios" => 2,
    "computersystem" => 3,
    "baseboard" => 4,
    "processor" => 5,
    "memorymodule" => 6,
    "fru" => 7,
    "mediadevice" => 8,
    "portablebattery" => 9,
    "vproverificationtable" => 10,
    "amtinformation" => 11
);

my %hwasset_bios_characteristics = (
    0x1		=> "Reserved_1",
    0x2		=> "Reserved_2",
    0x4		=> "Unknown",
    0x8		=> "BIOS characteristics not supported",
    0x10	=> "ISA",
    0x20	=> "MCA",
    0x40	=> "EISA",
    0x80	=> "PCI",
    0x100	=> "PC_card",
    0x200	=> "PnP",
    0x400	=> "APM",
    0x800	=> "BIOS upgradeable",
    0x1000	=> "BIOS shadowing allowed",
    0x2000	=> "VL VESA",
    0x4000	=> "ESCD",
    0x8000	=> "Boot from CD",
    0x10000	=> "Selectable boot",
    0x20000	=> "BIOS ROM socketed",
    0x40000	=> "Boot from PC card",
    0x80000	=> "EDD spec",
    0x100000	=> "int13h floppy for NEC 9800",
    0x200000	=> "int13h floppy for Toshiba",
    0x400000	=> "int13h 5.25.in 360 kb floppy",
    0x800000	=> "int13h 5.25 in 1.2 mb floppy",
    0x1000000	=> "int13h 3.5 in 720 kb floppy",
    0x2000000	=> "int13h 3.5 in 2.88 mb floppy",
    0x4000000	=> "int5h print screen services",
    0x8000000	=> "int9h 8042 keyboard services",
    0x10000000	=> "int14h serial services",
    0x20000000	=> "int17h printer services",
    0x40000000	=> "int10h CGA and mono video",
    0x80000000	=> "NEC PC 98"
);

my %hwasset_cpu_family = (
    0x1 => "Other",
    0x2 => "Unknown",
    20 => "Intel(R) Celeron(R) M processor",
    21 => "Intel(R) Pentium(R) 4 HT processor",
    40 => "Intel(R) Core(TM) Duo processor",
    41 => "Intel(R) Core(TM) Duo mobile processor",
    42 => "Intel(R) Core(TM) Solo mobile processor",
    43 => "Intel(R) Atom(TM) processor",
    130 => "Itanium(TM) Processor",
    161 => "Quad-Core Intel(R) Xeon(R) processor 3200 Series",
    162 => "Dual-Core Intel(R) Xeon(R) processor 3000 Series",
    163 => "Quad-Core Intel(R) Xeon(R) processor 5300 Series",
    164 => "Dual-Core Intel(R) Xeon(R) processor 5100 Series",
    165 => "Dual-Core Intel(R) Xeon(R) processor 5000 Series",
    166 => "Dual-Core Intel(R) Xeon(R) processor LV",
    167 => "Dual-Core Intel(R) Xeon(R) processor ULV",
    168 => "Dual-Core Intel(R) Xeon(R) processor 7100 Series",
    169 => "Quad-Core Intel(R) Xeon(R) processor 5400 Series",
    170 => "Quad-Core Intel(R) Xeon(R) processor",
    171 => "Dual-Core Intel(R) Xeon(R) processor 5200 Series",
    172 => "Dual-Core Intel(R) Xeon(R) processor 7200 Series",
    173 => "Quad-Core Intel(R) Xeon(R) processor 7300 Series",
    174 => "Quad-Core Intel(R) Xeon(R) processor 7400 Series",
    175 => "Multi-Core Intel(R) Xeon(R) processor 7400 Series",
    176	=> "Pentium(R) III Xeon(TM)",
    177	=> "Pentium(R) III Processor with Intel(R) SpeedStep(TM) Technology",
    178	=> "Pentium(R) 4",
    179	=> "Intel(R) Xeon(TM)",
    181	=> "Intel(R) Xeon(TM) processor MP",
    184 => "Intel(R) Itanium(R) 2 processor",
    185 => "Intel(R) Pentium(R) M processor",
    186 => "Intel(R) Celeron(R) D processor",
    187 => "Intel(R) Pentium(R) D processor",
    188 => "Intel(R) Pentium Processor Extreme Edition",
    189 => "Intel(R) Core(TM) Solo Processor",
    190 => "Intel(R) Core(TM)2 Duo Processor",
    191 => "Intel(R) Core(TM) 2 Duo Processor",
    192 => "Intel(R) Core(TM)2 Solo processor",
    193 => "Intel(R) Core(TM)2 Extreme processor",
    194 => "Intel(R) Core(TM)2 Quad processor",
    195 => "Intel(R) Core(TM)2 Extreme mobile processor",
    196 => "Intel(R) Core(TM)2 Duo mobile processor",
    197 => "Intel(R) Core(TM)2 Solo mobile processor",
    198 => "Intel(R) Core(TM) i7 processor",
    199 => "Dual-Core Intel(R) Celeron(R) processor",
    205	=> "Intel(R) Core(TM) i5 processor",
    206	=> "Intel(R) Core(TM) i3 processor",
    214 => "Multi-Core Intel(R) Xeon(R) processor",
    215 => "Dual-Core Intel(R) Xeon(R) processor 3xxx Series",
    216 => "Quad-Core Intel(R) Xeon(R) processor 3xxx Series",
    218 => "Dual-Core Intel(R) Xeon(R) processor 5xxx Series",
    219 => "Quad-Core Intel(R) Xeon(R) processor 5xxx Series",
    221 => "Dual-Core Intel(R) Xeon(R) processor 7xxx Series",
    222 => "Quad-Core Intel(R) Xeon(R) processor 7xxx Series",
    223 => "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
    224	=> "Multi-Core Intel(R) Xeon(R) processor 3400 Series"
);

my %hwasset_cpu_upg_info = (
    0x1 => "Other",
    0x2 => "Unknown",
    0x3 => "Daughterboard",
    0x4 => "ZIF socket",
    0x5 => "Replaceable Piggyback",
    0x6 => "Not upgradeable",
    0x7 => "LIF socket",
    0x8 => "Slot 1",
    0x9 => "Slot 2",
    0xA => "Socket 370",
    0xC => "Slot M",
    0xD => "Socket 423",
    0xF => "Socket 478",
     16 => "Socket 754",
     17 => "Socket 940",
     18 => "Socket 939",
     19 => "Socket mPGA604",
     20 => "Socket LGA771",
     21 => "Socket LGA775",
     22 => "Socket S1",
     23 => "Socket AM2",
     24 => "Socket F (1207)",
     25 => "Socket LGA1366",
     29 => "Socket LGA1156",
     30 => "Socket LGA1567",
     31 => "Socket PGA988A",
     32 => "Socket BGA1288"
);

my %hwasset_mem_form_fact = (
    1 => "Other",
    2 => "Unknown",
    3 => "SIMM",
    4 => "SIP",
    5 => "Chip",
    6 => "DIP",
    7 => "ZIP",
    8 => "Proprietary Card",
    9 => "DIMM",
    10 => "TSOP",
    11 => "Row of chips",
    12 => "RIMM",
    13 => "SODIMM",
    14 => "SRIMM",
    15 => "FB-DIMM"
);

my %hwasset_mem_type = (
    1 => "Other",
    2 => "Unknown",
    3 => "DRAM",
    4 => "EDRAM",
    5 => "VRAM",
    6 => "SRAM",
    7 => "RAM",
    8 => "ROM",
    9 => "FLASH",
    10 => "EEPROM",
    11 => "FEPROM",
    12 => "EPROM",
    13 => "CDRAM",
    14 => "3DRAM",
    15 => "SDRAM",
    16 => "SGRAM",
    17 => "RDRAM",
    18 => "DDR",
    19 => "DDR2",
    20 => "DDR2 FB-DIMM",
    24 => "DDR3",
    25 => "FBD2"
);

my %hwasset_mem_type_det = (
    0x1		=> "Reserved1",
    0x2		=> "Other",
    0x4		=> "Unknown",
    0x8		=> "FastPaged",
    0x10	=> "StaticColumn",
    0x20	=> "PseudoStatic",
    0x40	=> "RAMBUS",
    0x80	=> "Synchronous",
    0x100	=> "CMOS",
    0x200	=> "EDO",
    0x400	=> "WindowDRAM",
    0x800	=> "CacheDRAM",
    0x1000	=> "NonVolatile",
    0x2000	=> "Reserved2",
    0x4000	=> "Reserved3",
    0x8000	=> "Reserved4"
);

my %hwasset_media_cap82 = (
    0x1		=> "SMART feature set",
    0x2		=> "Security Mode feature set",
    0x4		=> "Removable Media feature set",
    0x8		=> "Mandatory Power Management feature set",
    0x10	=> "PACKET command feature set",
    0x20	=> "Write cache",
    0x40	=> "Look-ahead",
    0x80	=> "Release interrupt",
    0x100	=> "SERVICE interrupt",
    0x200	=> "DEVICE RESET command",
    0x400	=> "Host Protected Area feature set",
    0x800	=> "WRITE VERIFY command (Obsolete)",
    0x1000	=> "WRITE BUFFER command",
    0x2000	=> "READ BUFFER command",
    0x4000	=> "NOP command",
    0x8000	=> "IDENTIFY DEVICE DMA command (Obsolete)"
);
my %hwasset_media_cap83 = (
    0x1		=> "DOWNLOAD MICROCODE command",
    0x2		=> "READ/WRITE DMA QUEUED",
    0x4		=> "CFA feature set",
    0x8		=> "Advanced Power Management feature set",
    0x10	=> "Removable Media Status Notification feature set",
    0x20	=> "Power-Up In Standby feature set",
    0x40	=> "SET FEATURES subcommand required to spinup after power-up",
    0x80	=> "cap_word83_bit7",
    0x100	=> "SET MAX security extension",
    0x200	=> "Automatic Acoustic Management feature set",
    0x400	=> "48-bit Address feature set",
    0x800	=> "Device Configuration Overlay feature set",
    0x1000	=> "Mandatory FLUSH CACHE command",
    0x2000	=> "FLUSH CACHE EXT command",
#    0x4000	=> "Shall be set to one",
    0x8000	=> "Shall be cleared to zero"
);
my %hwasset_media_cap84 = (
    0x1		=> "SMART error logging",
    0x2		=> "SMART self-test",
    0x4		=> "Media serial number",
    0x8		=> "Media Card Pass Through Command feature set",
    0x10	=> "Streaming feature set",
    0x20	=> "General Purpose Logging feature set",
    0x40	=> "WRITE DMA FUA EXT and WRITE MULTIPLE FUA EXT",
    0x80	=> "WRITE DMA QUEUED FUA EXT command",
    0x100	=> "World wide name",
    0x200	=> "URG bit supported for READ STREAM DMA/PIO",
    0x400	=> "URG bit supported for WRITE STREAM DMA/PIO",
    0x800	=> "Time-limited R/W feature set",
    0x1000	=> "Time-limited R/W feature set R/W continuous enabled",
    0x2000	=> "cap_word84_bit13",
#    0x4000	=> "Shall be set to one",
    0x8000	=> "Shall be cleared to zero"
);

my %auditable_app_id = (
    16	=> "Security Admin",
    17	=> "Remote Control Operation",
    18	=> "Redirection Manager",
    19	=> "Firmware Update Manager",
    20	=> "Security Audit Log",
    21	=> "Network Time",
    22	=> "Network Administration",
    23	=> "Storage Administration",
    24	=> "Event Manager",
    25	=> "Circuit Breaker Manager",
    26	=> "Agent Presence Manager",
    27	=> "Wireless Configuration",
    28	=> "EAC"
);
my %audit_event_id = (
  16 => {
    0 =>  "AMT Provisioning Started",
    1 =>  "AMT Provisioning Completed",
    2 =>  "ACL Entry Added",
    3 =>  "ACL Entry Modified",
    4 =>  "ACL Entry Removed",
    5 =>  "ACL Access with invalid credentials",
    6 =>  "ACL Entry Enabled",
    7 =>  "TLS State Changed",
    8 =>  "TLS Server Certificate Set",
    9 =>  "TLS Server Certificate Removed",
    10 => "TLS Trusted Root Certificate Added",
    11 => "TLS Trusted Root Certificate Removed",
    12 => "TLS Pre-shared Key Set",
    13 => "Kerberos Settings Modified",
    14 => "Kerberos Master Key Modified",
    15 => "Flash Wear-Out Counters Reset",
    16 => "Power Package Modified",
    17 => "Set realm Authentication mode"
  },
  17 => {
    0 => "Performed Power-Up",
    1 => "Performed Power-Down",
    2 => "Performed Power-Cycle",
    3 => "Performed Reset",
    4 => "Boot Options"
  },
  18 => {
    0 => "IDE-R Session Opened",
    1 => "IDE-R Session Closed",
    2 => "IDE-R Enabled",
    3 => "IDE-R Disabled",
    4 => "SoL Session Opened",
    5 => "SoL Session Closed",
    6 => "SoL Enabled",
    7 => "SoL Disabled"
  },
  19 => {
    0 => "Firmware Updated",
    1 => "Firmware Update Failed"
  },
  20 => {
    0 => "Security Audit Log Cleared",
    1 => "Security Audit policy modified",
    2 => "Security Audit Log Disabled",
    3 => "Security Audit Log Enabled",
    4 => "Security Audit Log Exported",
    5 => "Security Audit Log Recovered (AMT 5.1+)"
  },
  21 => {
    0 => "AMT Time Set"
  },
  22 => {
    0 => "TCP/IP Parameters Set",
    1 => "Host Name Set",
    2 => "Domain Name Set",
    3 => "VLAN Parameters Set",
    4 => "Link Policy Set"
  },
  23 => {
    0 => "Global Storage Attributes Set",
    1 => "Storage EACL Modified",
    2 => "Storage FPACL Modified",
    3 => "Storage Write Operation"
  },
  24 => {
    0 => "Alert Subscribed",
    1 => "Alert Unsubscribed",
    2 => "Event Log Cleared",
    3 => "Event Log Frozen"
  },
  25 => {
    0 => "CB Filter Added",
    1 => "CB Filter Removed",
    2 => "CB Policy Added",
    3 => "CB Policy Remove",
    4 => "CB Default Policy Set",
    5 => "CB Heuristics Option Set",
    6 => "CB Heuristics State Cleared"
  },
  26 => {
    0 => "Agent Watchdog Added",
    1 => "Agent Watchdog Removed",
    2 => "Agent Watchdog Action set"
  },
  27 => {
    0 => "Wireless profile added",
    1 => "Wireless profile removed",
    2 => "Wireless profile updated"
  },
  28 => {
    0 => "EAC Posture Signer SET",
    1 => "EAC Enabled",
    2 => "EAC Disabled",
    3 => "EAC Posture State update",
    4 => "EAC Set Options"
  }
);

my %audit_storage_policy_get = (
    0 => "No Roll Over",
    1 => "Roll Over",
    2 => "Restricted Roll Over"
);
my %audit_storage_policy_set = (
    "no_ro" => 0,
    "rollover" => 1,
    "restrict_ro" => 2
);

my %signing_mechanism = (
    0 => "RSA_SHA-1",
    1 => "RSA_SHA-256",
    2 => "RSA_SHA-384"
);

my %audit_lock_type = (
    "lock"	=> 0,
    "unprovis"	=> 1,
    "unlock"	=> 2
);


#############################################################################
# soap setup

my ( $nas, $sas, $rcs, $gis, $nts, $ems, $has, $als );

sub SOAP::Transport::HTTP::Client::get_basic_credentials {
	return $main::amt_user => $main::amt_pass;
}

sub soap_init() {
    my $proxybase = "http://$amt_host:$amt_port";

    die("no AMT PW found\n") unless defined($main::amt_pass);

    $amt_host =~ m/^([a-z0-9_.-]+)(:([0-9]{1,5}))?$/i;
    if( defined($3) ) {
	$amt_host = $1;
	$amt_port = $3;
    }

    $nas = SOAP::Lite->new(
		proxy      => "$proxybase/NetworkAdministrationService",
		default_ns => "$schemabase/NetworkAdministration/2004/01");
    $sas = SOAP::Lite->new(
		proxy      => "$proxybase/SecurityAdministrationService",
		default_ns => "$schemabase/SecurityAdministration/2004/01");
    $rcs = SOAP::Lite->new(
		proxy      => "$proxybase/RemoteControlService",
		default_ns => "$schemabase/RemoteControl/2004/01");
    $gis = SOAP::Lite->new(
		proxy      => "$proxybase/GeneralInfoService",
		default_ns => "$schemabase/GeneralInfo/2004/01");
    $nts = SOAP::Lite->new(
		proxy      => "$proxybase/NetworkTimeService",
		default_ns => "$schemabase/NetworkTime/2004/01");
    $ems = SOAP::Lite->new(
		proxy      => "$proxybase/EventManagerService",
		default_ns => "$schemabase/Events/2004/01");
#    $fus = SOAP::Lite->new(
#		proxy      => "$proxybase/FirmwareUpdateService",
#		default_ns => "$schemabase/FirmwareUpdate/2004/01");
    $has = SOAP::Lite->new(
		proxy      => "$proxybase/HardwareAssetService",
		default_ns => "$schemabase/HardwareAsset/2004/01");
    $als = SOAP::Lite->new(
		proxy      => "$proxybase/AuditLogService",
		default_ns => "$schemabase/AuditLog/2008/01");
}

#--------------------------------------------------------------------------------------
sub soap_fault {
    my( $soap, $res ) = @_;

    if( ref $res ) {
	print "\nRequest FAULT. Code=", $res->faultcode, ". Details: '", $res->faultdetail,
		"'. Fault string: '". $res->faultstring. "'.\n\n";
    } else {
	print "\n  FAULT: ", $soap->transport->status, "\n";
    }
    exit 1;
}

#--------------------------------------------------------------------------------------
sub soap_debug {
    my ($in) = @_;

    if( ($amt_debug & 2  && ref($in) eq "HTTP::Response") ||
	($amt_debug & 1  && ref($in) eq "HTTP::Request") ) {
	print STDERR "\n--\n", $in->content, "--\n";
    }
}


#############################################################################
# functions

sub print_usage_header {
    printf STDERR <<EOF
  amttool 1.6     Can talk to Intel AMT managed devices.

  Usage: amttool [<params>]  <hostname>[:<port>]  <command>
  Parameters (before the hostname): --force|-f, --quiet|-q, --debug|-d, --ddebug|-dd

EOF
	if (!$use_header);
    $use_header++;
}

sub usage()
{
    print_usage_header();
    print STDERR <<EOF;
  Commands:
   help    - detailed commands help (except remote control)
   info    - general,audit,remote info (default command, when only hostname given)
   state   - show just the power state (S0 .. S5)
   hwasset - prints hardware asset data ('hwasset help' for details)
EOF
   print STDERR <<EOF unless $main::raised_priv;
   net     - iAMT device network administration ('net help' for details)
   time    - get/sync iAMT device time ('time help' for details)
   user    - access control management ('user help' for details)
   audit   - Access Monitor/Audit (AMT ver. 4.0+, 'audit help' for details)
EOF
    print STDERR <<EOF;
   <remote control commands> - details below.

  Remote Control Commands:
   powerup [params]     - turn on machine
EOF
   print STDERR <<EOF unless $main::raised_priv;
   powerdown            - turn off machine
   setbootopt [params]  - boot options for the next graceful shutdown or restart
   powercycle [params]  - powercycle machine
   reset [params]       - reset machine

   Boot params: {pxe|hd|hdsafe|diag|cd|iderflop|idercd|reflash|biossetup|biospause}

   Boot     lckpwr - lock power button      vdef - verbosity: default
   options  lckrst - lock reset button      vvrb - verbosity: verbose
            lckslp - lock sleep button      vqut - verbosity: quiet
            lckkbd - lock keyboard          vbln - verbosity: screen blank
            pwdbps - user password bypass   evnt - forced progress events
            cfgres - config. data reset     sol  - serial-over-lan (use 'amtterm')
EOF
    print STDERR <<EOF;

  Environment variables:
    AMT_USER - AMT username ('admin' if not set)
    AMT_PASSWORD - AMT Password
    HTTP_proxy - link to proxy in format: http://[proxy_user:pass@]<proxy_addr>:<port>

EOF
}
#--------------------------------------------------------------------------------------
sub get_user_confirm() {
    return 1 if( $param_force );

    printf " [y/N] ? ";
    my $reply = <stdin>;
    return 1 if( $reply =~ m/^(y|yes)$/i);

    printf "canceled.\n";
    return 0;
}
#--------------------------------------------------------------------------------------
sub print_res_report($) {
    my $ret = shift;
&set_alarm("1");
    if( defined($ret->result) ) {
	printf "".( $ret->result == $pt_status{'PT_STATUS_SUCCESS'} ? "done" : "error").".\n";
	return $ret->result;
    }
    printf "failure.\n";
    return -1;
}
#--------------------------------------------------------------------------------------
sub print_commit_warn {
    my $ret = shift;
&set_alarm("2");

    if (defined($ret) && $ret->result == $pt_status{'PT_STATUS_SUCCESS'}) {
	printf " * Use 'net commit' to save changes!\n\n";
    }
}
#--------------------------------------------------------------------------------------
sub print_result($) {
    my $ret = shift;
    my $rc = $ret->result;
&set_alarm("3");

    if( $rc == $pt_status{'PT_STATUS_SUCCESS'} ) {
	printf "done.\n";
	return 0;
    }

    printf "error: 0x%x (", $rc;
    if( !defined($pt_status_desc{$rc}) ) {
	printf "unknown status code).\n";
    } else {
	printf $pt_status_desc{$rc}.").\n";
    }
    return $rc;
}
#--------------------------------------------------------------------------------------
sub print_paramsout($) {
    my $ret = shift;
    my @paramsout = $ret->paramsout;
    print "params: " . join(", ", @paramsout) . "\n";
}
#--------------------------------------------------------------------------------------
sub print_hash {
    my $hash = shift;
    my $in = shift;
    my $wi = shift;

    foreach my $item (sort keys %{$hash}) {
	if (ref($hash->{$item}) eq "HASH") {
#		printf "%*s%s\n", $in, "", $item;
		next;
	}
	printf "%*s%-*s%s\n", $in, "", $wi, $item, $hash->{$item};
    }
}
#--------------------------------------------------------------------------------------
sub print_uuid {
    my ($q) = @_;
    my @n = @{ $q->{'Byte'} };

    printf "%02x%02x%02x%02x-%02x%02x-%02x%02x-", $n[3], $n[2], $n[1], $n[0],
	    $n[5], $n[4], $n[7], $n[6];
    printf "%02x%02x-", $n[8], $n[9];
    for my $i ( 10 .. 15 ) {
	printf "%02x", $n[$i]; }
    print "\n";
}
#--------------------------------------------------------------------------------------
sub conv_ipv4_to_txt {
    my $ipv4 = shift;

    my $addr = sprintf("%d.%d.%d.%d",
		$ipv4 / 256 / 256 / 256,
		$ipv4 / 256 / 256 % 256,
		$ipv4 / 256 % 256,
		$ipv4 % 256);

    return $addr;
}
#--------------------------------------------------------------------------------------
sub print_date_time {
	my $time = shift;
	my $in = shift;
	my $shift = shift;
	$shift = 0 - $main::tz unless defined $shift;

	my @tmp = localtime( $time + $shift );
	printf "%*s%02s/%02s/%s %02s:%02s", $in, "",
		$tmp[3], $tmp[4]+1, $tmp[5]+1900, $tmp[2], $tmp[1];
}
#--------------------------------------------------------------------------------------
sub print_hash_ipv4 {
    my $hash = shift;
    my $in = shift;
    my $wi = shift;

    foreach my $item (sort keys %{$hash})
    {
	printf "%*s%-*s", $in, "", $wi, $item;
	my $val = $hash->{$item};
	if ( $val =~ m/[0-9]+/ ) {
	    printf conv_ipv4_to_txt( $val )."\n";
	} else {
	    printf "$val\n";
	}
    }
}

#-----------------------------------------------------------------------------------------
sub do_soap {
    my $soap = shift;
    my $name = shift;
    my @args = @_;

    my $method = SOAP::Data ->name($name)
			    ->attr( { xmlns => $soap->ns } );
    my $ret = $soap->call( $method, @args );
&set_alarm("A");

    return print_result($ret);
}
#-----------------------------------------------------------------------------------------
sub check_amt_version {
    my ( $major, $minor, $verb ) = @_;

    $amt_version = $gis->GetCoreVersion()->paramsout;
    $amt_version =~ m/^(\d+).(\d+)/;
    return 1 if $1 > $major;
    return 1 if $1 == $major && $2 >= $minor;

    if( defined($verb)) {
	printf "$verb: " if ($verb !~ m/^v(erb)?$/i);
	printf "version mismatch (need $major.$minor+, have $amt_version)\n";
    }
    return 0;
}
#======================================================================================

#--------------------------------------------------------------------------------------
sub print_info_general() {
	my (@ret, $ret, @tmp);

	my $hostname = $gis->GetHostName()->paramsout;
&set_alarm("B");
#	my $domainname = $nas->GetDomainName()->paramsout;
#	printf " Hostname:     %s%s\n", $hostname, ("$domainname" ne "") ? ".$domainname" : "";
	printf " Hostname:     %s\n", $hostname;

	$amt_version = $gis->GetCoreVersion()->paramsout;
	printf " AMT version:  %s\n", $amt_version;

&set_alarm("C");
	if ( check_amt_version(2,0) ) {
	    @ret = $gis->GetCodeVersions()->paramsout;

&set_alarm("D");
	    foreach my $item (@ret) {
		if ( ref($item) eq "HASH" ) {
		    printf "%*s%s: %-*s\n", 3, "", $item->{'Description'}, 15, $item->{'Version'};
		} else {
		    printf " BIOS version: %s\n", $item;
		}
	    }
	}

&set_alarm("E");
	$ret = $gis->GetProvisioningMode()->paramsout;
	printf " Provisioning mode:  %s\n", $ret;
&set_alarm("F");

	$ret = $gis->GetProvisioningState()->paramsout;
	printf " Provisioning state: %s\n", $provis_state{$ret};
&set_alarm("G");

	$ret = $gis->GetVlanParameters();
	print " VLan",
		($ret->valueof('//VlanMode') eq "true" ? " Tag: ".$ret->valueof('//VlanTag') : ": Disabled");

&set_alarm("H");
	@ret = $gis->GetConfigServerInfo()->paramsout;
	printf "\n Config Server (IP:port): %s:%s\n", $ret[0], $ret[1];
&set_alarm("I");

	if ( check_amt_version(2,0) )
	{
	    $ret = $gis->GetAdminAclEntryStatus()->paramsout;
	    print " Local 'admin' password: ",
		    ( $ret eq "true" ? "never changed" : "changed from default"), ".\n";
&set_alarm("J");

	    $ret = $gis->GetAdminNetAclEntryStatus()->paramsout;
	    print " Remote 'admin' password: ",
		    ( $ret eq "true" ? "never changed" : "changed from default"), ".\n";
&set_alarm("K");

	    $ret = $gis->GetPasswordModel()->paramsout;
	    printf " Password model: %s\n", $pwd_model{$ret};

&set_alarm("L");
	    @ret = $gis->GetEnabledInterfaces()->paramsout;
	    printf " Enabled Interfaces: ". join(", ", @ret) ."\n";

&set_alarm("N");
	    $ret = $gis->GetNetworkState()->paramsout;
	    printf " Network State: %s\n", ($ret eq "true") ? "Enabled" : "Disabled";

&set_alarm("O");
	    @ret = $gis->GetSecurityParameters()->paramsout;
	    print " Security parameters:",
		"\n   Enterprise Mode: ", $ret[0],
		"\n   Tls Enabled: ", $ret[1],
		"\n   HwCrypto Enabled: ", $ret[2],
		"\n   Provisioning State: ", $provis_state{$ret[3]},
		"\n   Network Interface Enabled: ", $ret[4],
		"\n   SOL Enabled: ", $ret[5],
		"\n   IDER Enabled: ", $ret[6],
		"\n   FWUpdate Enabled: ", $ret[7],
		"\n   Link Is Up: ", $ret[8], "\n";

	    @ret = $gis->GetIderSessionLog()->paramsout;
	    print " IDE-R Sessions Log:", (@ret ? "" : " empty"), "\n";

	    foreach my $item (@ret) {
		print_date_time( $item->{'TimeStamp'}, 3 );
		printf " - %s:%s\n", conv_ipv4_to_txt( $item->{'ConsoleAddress'} ),
			$item->{'Port'};
	    }
	}

&set_alarm("P");
	if ( check_amt_version(5,0) ) {
	    @ret = $gis->GetMeCapabilities()->valueof('//Data');
	    print " ME Capabilities:\n"
		if (@ret);

	    foreach my $i ( @ret ) {
		printf "%*s%-*s\t%s\t(ver.: %d.%d)\n", 3, "", 14, $me_capabilities{ $i->{'Capability'} },
			( $i->{'State'} ? "Enabled " : "Disabled"),
			$i->{'Version'}->{'Major'}, $i->{'Version'}->{'Minor'};
	    }
	}

&set_alarm("Q");
	if ( check_amt_version(4,0) ) {
	    $ret = $gis->GetUuid()->valueof('//UUID');
	    print " System UUID: ";
	    print_uuid( $ret );
	    printf "\n";
&set_alarm("R");
	}
}

#--------------------------------------------------------------------------------------
sub print_info_remote() {
	my $quick = @_;
	my $powerstate = $rcs->GetSystemPowerState()->paramsout;
	my $extra = ($quick) ? " for $amt_host" : "";
&set_alarm("F");
	printf " Powerstate:   %s%s\n", $ps [ $powerstate & 0x0f ], $extra;

	unless ($quick) {
	my @rccaps = $rcs->GetRemoteControlCapabilities()->paramsout;
	printf " Remote Control Capabilities:\n";
	printf "   Control Commands Supported     %s%s%s%s\n",
		$rccaps[3] & 0x01 ? "powerCycle " : "",
		$rccaps[3] & 0x02 ? "powerDown "  : "",
		$rccaps[3] & 0x03 ? "powerUp "    : "",
		$rccaps[3] & 0x04 ? "reset "      : "";
	printf "   IanaOemNumber                  %i\n", $rccaps[0];
	printf "   Special Commands Supported     %s%s%s%s%s\n",
		$rccaps[2] & 0x0100 ? "PXE-boot "         : "",
		$rccaps[2] & 0x0200 ? "HD-boot "          : "",
		$rccaps[2] & 0x0400 ? "HD-boot-safemode " : "",
		$rccaps[2] & 0x0800 ? "Diag-boot "        : "",
		$rccaps[2] & 0x1000 ? "CD-boot "          : "";
	if ( check_amt_version(2,0) ) {
		printf "   Special Commands (Oem)         %s%s%s%s%s\n",
			$rccaps[1] & 0x01 ? "IDER "        : "",
			$rccaps[1] & 0x02 ? "SOL "         : "",
			$rccaps[1] & 0x04 ? "BiosReflash " : "",
			$rccaps[1] & 0x08 ? "BiosSetup "   : "",
			$rccaps[1] & 0x10 ? "BiosPause "   : "";
	} else {
		printf "   Special Commands (Oem)         %s%s%s%s%s\n",
			$rccaps[1] & 0x001 ? "IDER "         : "",
			$rccaps[1] & 0x002 ? "SOL "          : "",
			$rccaps[1] & 0x004 ? "BiosReflash "  : "",
			$rccaps[1] & 0x008 ? "BiosSetup "    : "",
			$rccaps[1] & 0x010 ? "BiosPause "    : "",
			$rccaps[1] & 0x020 ? "NoFloppyBoot " : "",
			$rccaps[1] & 0x040 ? "NoCDBoot "     : "",
			$rccaps[1] & 0x100 ? "TerminalEmulationVT52 "    : "",
			$rccaps[1] & 0x200 ? "TerminalEmulationVT100+ "  : "",
			$rccaps[1] & 0x300 ? "TerminalEmulationVT-UTF8 " : "",
			$rccaps[1] & 0x400 ? "TerminalEmulationPC-ANSI " : "";
	}
	printf "   SystemFirmwareCapabilities     %s%s%s%s%s%s%s%s%s%s\n\n", 
		$rccaps[4] & 0x0002 ? "LockPowerButton "        : "",
		$rccaps[4] & 0x0004 ? "LockResetButton "        : "",
		$rccaps[4] & 0x0020 ? "LockSleepButton "        : "",
		$rccaps[4] & 0x0010 ? "LockKeyboard "           : "",
		$rccaps[4] & 0x0800 ? "UserPasswordBypass "     : "",
		$rccaps[4] & 0x1000 ? "ForcedProgressEvents "   : "",
		$rccaps[4] & 0x8000 ? "ConfigurationDataReset " : "",
		$rccaps[4] & 0x2000 ? "VerbosityVerbose "       : "",
		$rccaps[4] & 0x4000 ? "VerbosityQuiet "         : "",
		$rccaps[4] & 0x0001 ? "VerbosityScreenBlank "   : "";
	}
&set_alarm("F");
	return 0;
}
#--------------------------------------------------------------------------------------
sub cmnd_audit_info;

#--------------------------------------------------------------------------------------
sub cmnd_info {
    my $cmd = shift || '';
    $cmd = lc($cmd);

    if ($cmd eq "help") {
	print_usage_header();
	print "    info [<details>]\n        <details> - (gen[eral]|rem[ote]|aud[it]|a[udit]log), ".
	    "all info if <details> omited.\n\n";
	return 0;
    }

    print "\n### Host '$amt_host' :: AMT info\n";

    if( !$cmd ) {
	print_info_general;
	print_info_remote;
	cmnd_audit_info($cmd);     }
    elsif( $cmd =~ m/^gen(eral)?$/ ) {
	print_info_general;  }
    elsif( $cmd =~ m/^aud(it)?$/ ) {
	cmnd_audit_info($cmd);     }
    elsif( $cmd =~ m/^rem(ote)?$/ ) {
	print_info_remote;   }
    return 0;
}
#======================================================================================

#--------------------------------------------------------------------------------------
sub remote_control {
    my $command = shift;
    my @params  = @_;
    my $special = shift;
    my @args;

    my $i;
    my $sol = 0;
    my $run_sol = 0;
    my $boot_opts = 0;

#    printf "\nparams list (".($#params+1)."): " if (!$param_quiet);
    for $i ( 0 .. $#params ) {
#	printf "$params[$i] | " if (!$param_quiet);
	if ($params[$i] =~ m/^sol$/i) { $sol = 1; }
	if ($params[$i] =~ m/^runsol$/i) { $run_sol = 1; $sol = 1; }
	if ($main::raised_priv && defined( $rcbo{$params[$i]} )) { $boot_opts += $rcbo{$params[$i]}; }
    }
    printf "\n" if (!$param_quiet);

    printf "host %s: %s%s$main::raised_priv  ", $amt_host, $command, 
			( defined($special) && (defined($rccs{$special}) ||
			  defined($rccsp{$special}) )) ? ", $special":"";

    return -1  if( !get_user_confirm() );

#    printf "\nexecute: %s\n", $command if (!$param_quiet);
    push @args, SOAP::Data->name('Command' => $rcc{$command});
    push @args, SOAP::Data->name('IanaOemNumber' => "343");

    if (defined($special)) {
	if (defined($rccs{$special})) {
	    push @args, SOAP::Data->name('SpecialCommand' => $rccs{$special} );
	}
	elsif (defined($rccsp{$special}) ) {
	    push @args, SOAP::Data->name('SpecialCommand' => 0xC1 );
	    push @args, SOAP::Data->name('SpecialCommandParameter' => $rccsp{$special} );
	}
    }
    push @args, SOAP::Data->name('OEMparameters' => $rccp_oem{"sol"} )
	if ($command ne "powerdown" && $sol);
    push @args, SOAP::Data->name('BootOptions' => $boot_opts)
	if ($boot_opts);

    do_soap( $rcs, "RemoteControl", @args );

    sleep 1 if ( $run_sol );
    system( '/usr/local/bin/amtterm -q -u '.$main::amt_user." ".$amt_host." ".$amt_port)
	if ( $run_sol );
    return 0;
}


#=====================================================================================
sub cmnd_time {
    my $cmd    = shift;
    my @params = @_;

    if( lc($cmd) eq "help" ) {
	print_usage_header();
	printf "    time [set]\n        get time from AMT device, with 'set' sync AMT-dev time with local\n\n";
	return 0;
    }

    printf "## Host '$amt_host':\n";

    my ($ta0, $tm1, $tm2, $ret, );

    if ( check_amt_version(2,0) ) {
	$ta0 = $nts->GetLowAccuracyTimeSynch()->valueof('//Ta0');
    } else {
	$ta0 = $ems->GetEventLogTimestampClock()->valueof('//Time');;
    }
    $tm1 = time();

    printf "  Time on the Intel AMT device: ";
    print_date_time $ta0, 0;

    printf "\n  Time on the local machine:    ";
    print_date_time $tm1, 0, 0;
    printf "\n\n";

    return 0 if( !defined($cmd) || $cmd ne "set");

    printf "Are you sure to synchronize AMT time with the local";
    return -1 if !get_user_confirm();

    printf "Syncing: ";
    my @args;

    if ( check_amt_version(2,0)) {
	push @args, SOAP::Data->name('Ta0' => $ta0);
	push @args, SOAP::Data->name('Tm1' => $tm1+$main::tz);
	$tm2 = time()+$main::tz;
	push @args, SOAP::Data->name('Tm2' => $tm2);
	$ret = do_soap($nts, "SetHighAccuracyTimeSynch", @args);
    } else {
	$tm2 = time();
	push @args, SOAP::Data->name('Time' => $tm2);
	$ret = do_soap($ems, "SetEventLogTimestampClock", @args);
    }
    return $ret;
}

#=====================================================================================
sub cmnd_net_info {
    my $cmd = shift;
    my ($arg, $ret, @if, %v);

    if ( lc($cmd) eq "help") {
	print_usage_header();
	print "    net info\n";
	return 0;
    }

    if ( check_amt_version(2,5) )
    {
	$ret = $nas->EnumerateInterfaces();
	@if = $ret->paramsout;

	foreach my $if (@if) {
	    $arg = SOAP::Data->name('InterfaceHandle' => $if);
	    $ret = $nas->GetInterfaceSettings($arg);

	    printf "  Network Interface Handle%*s\n", 4, $if;

	    %v = %{ $ret->paramsout };
	    printf "%*s%-*s ", 4, "", 24, "Description";
	    printf $v{'HardwareAddressDescription'} if (defined( $v{'HardwareAddressDescription'} ));
	    printf $v{'HardwareDescription'} if (defined( $v{'HardwareDescription'} ));
	    printf "\n%*s%-*s %s\n", 4, "", 24, "MAC Address", $v{'MACAddress'};
	    printf "%*s%-*s %s\n", 4, "", 24, "Interface Mode", $v{'InterfaceMode'};

	    my $pol = $v{'LinkPolicy'};
	    printf "%*s%-*s %s ", 4, "", 24, "Link Policy", $pol;
	    if ( $pol > 0 ) {
		my $s0 = ($pol & 0x01) || ($pol & 0x10);
		my $sx = ($pol & 0x0E) || ($pol & 0xE0);
		my $ac = ($pol & 0x01) || ($pol & 0x0E);
		my $dc = ($pol & 0x10) || ($pol & 0xE0);
		printf "(link operational if ME is, ".
			"power state %s%s%s on %s%s%s)\n", 
			( $s0 ) ? "S0" : "",
			( $s0 && $sx ) ? "/" : "",
			( $sx ) ? "Sx" : "",
			( $ac ) ? "AC" : "",
			( $ac && $dc ) ? "/" : "",
			( $dc ) ? "DC" : "";
	    }

	    printf "%*s%-*s %s\n", 4, "", 24, "DHCP", (($v{'DhcpEnabled'} eq "false") ? "Disabled" : "Enabled");

	    %v = %{ $ret->paramsout->{'IPv4Parameters'} };
	    printf "%*s%-*s %s\n", 7, "", 21, "IP/Mask", conv_ipv4_to_txt( $v{'LocalAddress'} )." / ".
							conv_ipv4_to_txt( $v{'SubnetMask'} );
	    printf "%*s%-*s %s\n", 7, "", 21, "Gateway", conv_ipv4_to_txt($v{'DefaultGatewayAddress'} );
	    printf "%*s%-*s %s\n", 7, "", 21, "DNS", conv_ipv4_to_txt( $v{'PrimaryDnsAddress'} )." ".
					    conv_ipv4_to_txt( $v{'SecondaryDnsAddress'} );
	}
    } else {
	# AMT prior to v. 2.5+
	$ret = $nas->GetTcpIpParameters();

	return -1 if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} );

	@if = $ret->paramsout;
	%v = %{$if[1]};
	printf "  Network Interface%-*sWired0\n", 10, "";
	printf "%*s%-*s %s\n", 4, "", 24, "DHCP", $net_dhcp_modes{ $if[0] };

	printf "%*s%-*s %s\n", 7, "", 21, "IP/Mask", conv_ipv4_to_txt( $v{'LocalAddress'} )." / ".
							conv_ipv4_to_txt( $v{'SubnetMask'} );
	printf "%*s%-*s %s\n", 7, "", 21, "Gateway", conv_ipv4_to_txt($v{'DefaultGatewayAddress'} );
	printf "%*s%-*s %s\n", 7, "", 21, "DNS", conv_ipv4_to_txt( $v{'PrimaryDnsAddress'} )." ".
					    conv_ipv4_to_txt( $v{'SecondaryDnsAddress'} );
#	printf "%*s%-*s %s\n", 7, "", 21, "DomainName", $v{'DomainName'};
    }
    return 0;
}

#--------------------------------------------------------------------------------------
sub ipv4_addr($$) {
    my ( $name, $ipv4 ) = @_;

    $ipv4 =~ m/(\d+).(\d+).(\d+).(\d+)/ or die "parse ipv4 address: $ipv4";
    my $num = $1 * 256 * 256 * 256 +
		$2 * 256 * 256 +
		$3 * 256 +
		$4;
    printf STDERR "ipv4 %-24s: %-16s -> %u\n", $name, $ipv4, $num
	if( $amt_debug & 0x1 );
    return SOAP::Data->name( $name => $num );
}

#======================================================================================
sub cmnd_net_conf {
    my $cmd = shift;
    my @prms = @_;

    my $if   = shift;
    my $link = shift;
    my $ip   = shift;
    my $mask = shift;
    my $gw   = shift;
    my $dns1 = shift;
    my $dns2 = shift;

    my ($mode, $ret, @ifdesc, @ipv4, @args);

    $cmd = lc($cmd);

    if ( $cmd eq "help" || !defined($if) || !defined($link) ||
			(defined($ip) && !defined($mask)) ) {
	print_usage_header();
	printf "    net conf <if> <policy> [<ip> <mask> [<gw> [<dns1> [<dns2>]]]]\n";
	
	if ( $cmd ne "help" ) {
	    printf "\t<if> - the interface handle (any digit for AMT prior to v. 2.5+)\n".
	    "\t<policy> - the link policy (any digit for AMT prior to v. 2.5+)\n".
	    "\t<ip> <mask> - the IPv4 address\n\n".
	    "\tWhen no IP configuration is specified the tool tries to configure AMT\n".
	    "\tin shared_mac_address mode with dhcp, otherwise\n".
	    "\tin separate_mac_address mode with static IP address.\n\n".
	    "\tDefaults: <dns1> - gateway address, <dns2> - 0.0.0.0.\n\n";
	}
	return 0;
    }

    # build common arguments struct
    if( defined($ip) ) {
	$mode = "SEPARATE_MAC_ADDRESS";
	$gw   = "0.0.0.0" if !defined($gw);
	$dns1 = $gw       if !defined($dns1);
	$dns2 = "0.0.0.0" if !defined($dns2);
	push @ipv4, ipv4_addr("LocalAddress", $ip);
	push @ipv4, ipv4_addr("SubnetMask", $mask);
	push @ipv4, ipv4_addr("DefaultGatewayAddress", $gw);
	push @ipv4, ipv4_addr("PrimaryDnsAddress", $dns1);
	push @ipv4, ipv4_addr("SecondaryDnsAddress", $dns2);
    } else {
	$mode = "SHARED_MAC_ADDRESS";
	# no ip info -- use DHCP
    }

    printf "  Setting IP configuration: ";

    return -1  if ( !get_user_confirm() );

    if( check_amt_version(2,5) )
    {
	push @ifdesc, SOAP::Data->name("InterfaceMode" => $mode);
	push @ifdesc, SOAP::Data->name("LinkPolicy" => $link);
	push @ifdesc, SOAP::Data->name("IPv4Parameters" => \SOAP::Data->value(@ipv4))
	    if @ipv4;

	push @args, SOAP::Data->name("InterfaceHandle" => $if);
	push @args, SOAP::Data->name("InterfaceDescriptor" => \SOAP::Data->value(@ifdesc));

	$ret = do_soap( $nas, "SetInterfaceSettings", @args );
    } else {
	# AMT prior v. 2.5
	
	# DomainName in StaticIPv4Parameters
	if( @ipv4 )
	{
	    push @args, SOAP::Data->name("DhcpMode" => $net_dhcp_modes_code{'DhcpModeDisabled'});
	    push @ipv4, SOAP::Data->name("DomainName", $nas->GetDomainName()->paramsout );
	    push @args, SOAP::Data->name("StaticIPv4Parameters" => \SOAP::Data->value(@ipv4));
	} else {
	    push @args, SOAP::Data->name("DhcpMode" => $net_dhcp_modes_code{'DhcpModeEnabled'});
	}
	$ret = do_soap( $nas, "SetTcpIpParameters", @args );
    }
    print_commit_warn( $ret );
    return $ret;
}

#-------------------------------------------------------------------------------------
sub cmnd_net_ping {
    my ( $cmd, $state ) = @_;
    my ( $arg, $ret );

    $cmd = lc($cmd);

    if ( $cmd eq "help" || ( defined($state) && $state !~ m/^(on|off)$/i) ) {
	print_usage_header();
	printf "    net ping [on|off]\n";
	return 0;
    }

    printf "  Ping response";

    if( !$state ) {
	$ret = $nas->GetPingResponse()->valueof('//enabled');
	print "  ", (($ret eq "false") ? "Disabled" : "Enabled"), "\n";
	return 0;
    }

    print " set to ", ($state eq "on" ? "Enabled" : "Disabled"), ": ";

    return -1  if( !get_user_confirm() );

    $arg = SOAP::Data->name( 'enabled' => ($state eq "on" ? 1 : 0) );
    return do_soap( $nas, "SetPingResponse", $arg );
}

#-------------------------------------------------------------------------------------
sub cmnd_net_hostname {
    my ( $cmd, $name ) = @_;
    my ( $arg, $ret );

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    net host[name] [host_name]\n";
	return 0;
    }

    print "  Hostname";

    if( ! $name ) {
	$ret = $nas->GetHostName()->valueof('//HostName');
	printf ":  $ret\n\n";
	return 0;
    }

    if( $name !~ m/^[a-z0-9_-]+$/i ) {
	printf "\n  Error. Wrong hostname: '$name'\n";
	return -2;
    }

    printf "  set to  '$name'";

    return -1  if ( !get_user_confirm() );

    $arg = SOAP::Data->name( 'HostName' => $name );
    $ret = $nas->SetHostName( $arg );

    print_result $ret ;
    print_commit_warn $ret;
    return $ret->result;
}

#-------------------------------------------------------------------------------------
sub cmnd_net_domain_name {
    my ( $cmd, $name ) = @_;
    my ( $arg, $ret );

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    net domain[name] [domain_name]\n\tuse '-' for empty domain name\n";
	return 0;
    }

    print "  Domainname";

    if( ! $name ) {
	$ret = $nas->GetDomainName()->valueof('//DomainName');
	print ": ", ($ret ? "not defined." : $ret), "\n";
	return 0;
    }

    if( $name !~ m/^([a-z0-9_-]\.)*[a-z0-9_-]/i ) {
	printf "\n  Error. Wrong domain name: $name\n";
	return -2;
    }

    if ( $name eq "-" ) {
	$name = "";
	printf " reset to empty string: ";
    }else {
	printf " set to '$name': ";
    }

    return -1  if ( !get_user_confirm() );

    $arg = SOAP::Data->name( 'DomainName' => $name );
    $ret = $nas->SetDomainName( $arg );

    print_result $ret;
    print_commit_warn $ret;
    return $ret->result;
}

#-------------------------------------------------------------------------------------
sub cmnd_net_commit {
    my $cmd    = shift;
    my @prms = @_;
    my $ret;

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	print "    net commit[changes]\n";
	print "\tCommits (save to flash) pending configuration made to the AMT with commands:\n",
		"\tSetTLSEnabled, SetTLSOptions, SetTLSKeyandCertificate, SetTLSPSK,\n",
		"\tSetTLSCertificate, net vlan, net config, net HostName, net DomainName";
	return 0;
    }

    print "  Commit net commands pending configuration: ";

    return -1  if ( !get_user_confirm() );
    return do_soap( $sas, "CommitChanges" );
}


#-------------------------------------------------------------------------------------
sub cmnd_net_vlan {
    my ( $cmd, $p1, $p2, $p3 ) = @_;
    my ( @arg, $ret, $tag );

    $cmd = lc($cmd);

    if( $cmd eq "help" || defined($p1) &&
	  ( ( $p1 ne "set" && $p1 !~ m/^[0-9]+$/ ) ||
	    ( $p1 eq "set" && (!defined($p2) || $p2 !~ m/^[0-9]+$/)) ||
	    ( defined($p2) && ( ( $p2 ne "set" && $p2 !~ m/^[0-9]+$/ ) ||
		$p2 eq "set" && (!defined($p3) || $p3 !~ m/^[0-9]+$/)) )
	  ) ) {
	print_usage_header();
	print "    net vlan [<iface>] [set <vlan_tag>]\n",
		"\tNOT supported by AMT ver. 2.5, 2.6, 4.x, 6.x+\n",
		"\tsome AMT ver. hangs with or without <iface>\n",
		"\t<vlan_tag>=1-4095 to enable and 0 to disable";
	return 0;
    }

    if( (!defined($p1) || $p1 ne "set") && !defined($p2) ) {
	print "  V-LAN";
	if( defined($p1) ) {
	    print " for interface ", $p1;
	    my $arg = SOAP::Data->name( 'InterfaceHandle' => $p1 );
	    $ret = $nas->GetVlanParameters( $arg );
	} else {
	    $ret = $nas->GetVlanParameters( ); }
	print "", ($ret->valueof('//VlanMode') eq "true" ? " Tag: ".$ret->valueof('//VlanTag') : ": Disabled"),
	    "\n\n";
	return $ret->result;
    }

    $tag = ($p1 eq "set" ? $p2 : $p3);
    if( $tag < 0 || $tag > 4095 ) {
	print "  Error. VLAN Tag number can be 1-4095 and 0 to disable.\n\n";
	return -2;
    }

    print "  Set VLAN Tag #", $tag, ($p1 ne "set" ? " for interface ".$p1 : ""), ": ";
    return -1  if ( !get_user_confirm() );

    push @arg, SOAP::Data->name( 'InterfaceHandle' => $p1 ) if( $p1 ne "set" );
    push @arg, SOAP::Data->name( 'VlanMode' => $tag && 1 );
    push @arg, SOAP::Data->name( 'VlanTag'  => $tag );
    $ret = $nas->SetVlanParameters( @arg );

    print_result $ret;
    print_commit_warn $ret;
    return $ret->result;
}

#--------------------------------------------------------------------------------------
sub cmnd_net {
    my @params = @_;
    my $cmd = shift;
    my $ret = -3;

    $cmd = lc($cmd);

    if( $cmd eq "help" || !@params ) {
	print_usage_header();
	$cmd = "help";
	@params = ( $cmd );
    } else {
	printf "## '$amt_host' :: Net Administration\n";
    }

    if ( $cmd =~ m/^(host(name)?|help)$/ ) {
	$ret = cmnd_net_hostname(@params);
    }
    if ( $cmd =~ m/^(domain(name)?|help)$/ ) {
	$ret = cmnd_net_domain_name(@params);
    }
    if ( $cmd =~ m/^(ping|help)$/ ) {
	$ret = cmnd_net_ping(@params);
    }
    if ( $cmd =~ m/^(info|help)$/ ) {
	$ret = cmnd_net_info(@params);
    }
    if ( $cmd =~ m/^(conf(ig)?|help)$/ ) {
	$ret = cmnd_net_conf(@params);
    }
    if ( $cmd =~ m/^(vlan|help)$/ ) {
	$ret = cmnd_net_vlan(@params);
    }
    if ( $cmd =~ m/^(commit(changes)?|help)$/ ) {
	$ret = cmnd_net_commit(@params);
    }

    if ($cmd eq "help") {
	print "\n\n";
    }

    if ($ret >= -2) {
	return $ret;
    } else {
	print " Unknown command: net $cmd\n";
	return 10;
    }
}
#=====================================================================================


#-------------------------------------------------------------------------------------
sub get_user_handle($) {
    my $srch_name = shift;
    my (@out, @usr, $ret, $arg, $totl, $start, $name);
    my $cnt = 0;

    return if (!defined($srch_name));

    do {
	$start = $cnt+1;
	$arg = SOAP::Data->name( 'StartIndex' => $start );
	$ret = $sas->EnumerateUserAclEntries( $arg );
	
	if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	    print_result($ret);
	    return;
	}
	@out = $ret->paramsout;
	$totl = $out[0];
	$cnt  = $out[1];

	if ( $totl == 0 && $cnt == 0 ) {
	    return;
	}
	if ($totl == 1) {
	    @usr = $out[2]->{'Handle'};
	} else {
	    @usr = @{$out[2]->{'Handle'}};
	}

	foreach my $usr_hndl (@usr) {
	    $arg = SOAP::Data->name( 'Handle' => $usr_hndl );

	    if ( check_amt_version(2,0) ) {
		# AMT v. 2.x+
		$ret = $sas->GetUserAclEntryEx( $arg )->paramsout;
		if ( defined($ret->{'DigestUser'}) ) {
		    $name = $ret->{'DigestUser'}->{'Username'};
		} else {
		    $name = decode_base64( $ret->{'KerberosUser'}->{'Sid'} );
		}
	    } else {
		# AMT 1.x
		$name = $sas->GetUserAclEntry( $arg )->paramsout->{'Username'};
	    }
	    return $usr_hndl if ( $srch_name eq $name );
	}
    } while ( $totl > $start+$cnt-1 );
}

#-------------------------------------------------------------------------------------
sub create_reals_arr( @ ) {
    my @txt_realms = @_;
    my @realms;

    foreach my $i (@txt_realms) {
	if ( defined( $user_acl_realm_codes{ lc($i) } )) {
	    push( @realms, SOAP::Data->name("Realm" =>
			$user_acl_realm_codes{ lc($i) } ));
	} elsif ( $i =~ m/^[0-9]{1,2}$/ && $i > 0 && $i < scalar @user_acl_realm) {
	    push( @realms, SOAP::Data->name("Realms" => $i ));
	} else {
	    printf "Error.  Wrong realm '$i'. 'net realm' for the list.\n";
	    return;
	}
    }
    return @realms;
}

#-------------------------------------------------------------------------------------
sub print_user_details( $ ) {
    my $usr_hndl = shift;
    my $arg = SOAP::Data->name( 'Handle' => $usr_hndl );
    my ( $name, $perm, $ret, @realms );

    if ( check_amt_version(2,0) ) {
	# AMT v. 2.x+
	$ret = $sas->GetUserAclEntryEx( $arg )->paramsout;
	if ( defined($ret->{'DigestUser'}) ) {
	    $name = $ret->{'DigestUser'}->{'Username'};
	    printf "%*s%-*s", 3, "", 22, $name;
	} else {
	    printf "%*sKerbSID: %-*s", 3, "", 18,
			decode_base64($ret->{'KerberosUser'}->{'Sid'});
	}
	if ( ref( $ret->{'Realms'}->{'Realm'} ) eq "ARRAY" ) {
	    $perm = scalar @{$ret->{'Realms'}->{'Realm'}};
	    @realms = @{$ret->{'Realms'}->{'Realm'}};
	} else {
	    $perm = 1;
	    $realms[0] = $ret->{'Realms'}->{'Realm'};
	}
	printf "%2d realm%-*s", $perm, 4, (($perm == 1) ? "" : "s");
	printf "%-*s", 25, $ret->{'AccessPermission'};

	if ( check_amt_version(3,0) ) {
	    $ret = $sas->GetAclEnabledState( $arg )->paramsout;
	    printf "%-*s", 9, ( ($ret eq "false") ? "Disabled" : "Enabled" );
	}
	if ($name =~ /^\$\$/) {
	    printf "<system account>";
	}
	printf "\n";
    } else {
	# AMT 1.x
	$ret = $sas->GetUserAclEntry( $arg );
	printf "   ".$ret->paramsout->{'Username'}."\n";
    }
    return @realms;
}

#-------------------------------------------------------------------------------------
sub prep_user_acl_entry( @ ) {
    my $amt2   = shift;
    my $name   = shift;
    my $access = shift;
    my @realms_arr = @_;
    my @args;

    if ( $amt2 ) {
	# AMT 2.0+
	my @dguser;
	my $hash = md5_base64( $name, ":",
		$sas->GetDigestRealm()->paramsout, ":",
		$ENV{'AMT_NEW_PASSWORD'} );

	push( @dguser, SOAP::Data->name( "Username" => $name ));
	push( @dguser, SOAP::Data->name( "DigestPassword" => $hash ));
	push( @args, SOAP::Data->name( "DigestUser" =>
			\SOAP::Data->value( @dguser ) ));
	$access = $acl_access_permission{$access}
	    if defined($acl_access_permission{$access});
	push( @args, SOAP::Data->name( "AccessPermission" => $access ));
    } else {
	# AMT 1.x
	push( @args, SOAP::Data->name( "Username" => $name ));
	push( @args, SOAP::Data->name( "Password" => $ENV{'AMT_NEW_PASSWORD'}) );
    }

    push( @args, SOAP::Data->name("Realms" =>
			\SOAP::Data->value( @realms_arr ) ));
    return @args;
}


#-------------------------------------------------------------------------------------
sub cmnd_user_list {
    my $cmd    = shift;

    my ($totl, $perm, $name, $start, @usr);
    my $cnt = 0;

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    user list\n".
		"        Prints users list and their info (except Primary Admin).\n";
	return 0;
    }

    do {
	$start = $cnt+1;
	my $arg = SOAP::Data->name( 'StartIndex' => $start );
	my $ret = $sas->EnumerateUserAclEntries( $arg );
	
	if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	    print_result($ret);
	    return $ret->result;
	}
	my @out = $ret->paramsout;
	$totl = $out[0];

	printf "$amt_host  Registered $totl AMT user account%s%s\n",
			(($totl == 1) ? "" : "s"),
			(($totl) ? ":" : ".")
	    if ( !$cnt );

	$cnt = $out[1];
	return 0 if ( $totl == 0 && $cnt == 0 );

	if ($totl == 1) {
	    @usr = $out[2]->{'Handle'};
	} else {
	    @usr = @{$out[2]->{'Handle'}};
	}

	foreach my $usr_hndl ( @usr ) {
	    print_user_details( $usr_hndl );
	}
    } while ( $totl > $start+$cnt-1 );
    printf "\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_user_admin {
    my $cmd    = shift;

    my $ret;

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    user admin\n".
		"        Prints Primary Admin username.\n";
	return 0;
    }

    $ret = $sas->GetAdminAclEntry();

    if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	print_result($ret);
	return $ret->result;
    }
    printf "$amt_host  Primary Admin username: %s\n", $ret->paramsout;
}

#-------------------------------------------------------------------------------------
sub cmnd_user_adminupd {
    my $cmd  = shift;
    my $name = shift;

    my (@args, $arg, $ret);

    $cmd  = lc($cmd);
    $name = $main::amt_user if (!defined($name));

    if ( $cmd eq "help" || $name eq "help" ||
	    !defined($ENV{'AMT_NEW_PASSWORD'}) ) {
	print_usage_header();
	printf "    user adminupd[ate] [<username>]\n".
		"        Change Primary Admin password and/or username.\n".
		"        Password is passed via AMT_NEW_PASSWORD environment variable.\n";
	return 0;
    }

    printf " '$amt_host' Change the PrimaryAdmin's %spassword: ",
		( ($name ne $main::amt_user) ? "username and " : "" );

    return -1 if ( ! get_user_confirm() );

    push @args, SOAP::Data->name("Username" => $name );

    if ( check_amt_version(2,0) ) {
	# AMT 2.0+
	my $hash = md5_base64( $name, ":",
		$sas->GetDigestRealm()->paramsout, ":",
		$ENV{'AMT_NEW_PASSWORD'} );

	push @args, SOAP::Data->name( "DigestPassword" => $hash );

	$ret = do_soap($sas, "SetAdminAclEntryEx",
			SOAP::Data->name( "EntryEx", \SOAP::Data->value(@args) ));
    } else {
	# AMT 1.x
	push @args, SOAP::Data->name( "Password" => $ENV{'AMT_NEW_PASSWORD'});

	$ret = do_soap($sas, "SetAdminAclEntry",
			SOAP::Data->name( "Entry", \SOAP::Data->value(@args) ));
    }
    return $ret;
}

#-------------------------------------------------------------------------------------
sub cmnd_user_enable {
    my $cmd  = shift;
    my $name = shift;
    my ( @args, $ret );

    $cmd = lc($cmd);

    if ( $cmd eq "help" ) {
	print_usage_header();
	printf "    user (en|dis)[able] <username>\t(AMT v. 3.0+)\n".
	return 0;
    }

    if ( ! check_amt_version(3,0) ) {
	printf " Enable/Disable a User ACL feature is supported in iAMT v. 3.0+\n";
	return -2;
    }

    printf "$amt_host  ";

    my $hndl = get_user_handle($name);

    if ( $hndl !~ m/[0-9]+/ ) {
	printf " Error: unknown username '%s'.\n", $name;
	return $pt_status{"PT_STATUS_INVALID_NAME"};
    }

    my $enabled = ($cmd =~ m/^en(able)?$/) ? "true" : "false";
    printf "  %sable user '%s' ", ($enabled eq "true" ? "En" : "Dis"), $name;
    return -1 if ( ! get_user_confirm() );

    push( @args, SOAP::Data->name("Handle"  => $hndl ));
    push( @args, SOAP::Data->name("Enabled" => $enabled ));

    return do_soap( $sas, "SetAclEnabledState", @args );
}


#-------------------------------------------------------------------------------------
sub cmnd_user_add {
    my $cmd  = shift;
    my $name = shift;
    my @realms = @_;
    my ( @realms_arr, $access );

    if ($cmd eq "help" || !defined($name) || !@realms) {
	print_usage_header();
	printf "    user add <username> [<access>] <realms>\n".
		"        <access> - (network|local|any) only for AMT 2.0+\n".
		"          defaults to 'network'\n".
		"        <realms> - space separated list or realms\n".
		"          or their codes ('net realm' for the list)\n".
		"        Password is passed via AMT_NEW_PASSWORD environment variable.\n";
	return 0;
    }

    my $amt2 = check_amt_version(2,0);

    if ( $amt2 ) {
	$access = shift;
	$access = lc $access;
	if ( defined($acl_access_permission{ lc($access) }) ) {
	    $access = $acl_access_permission{ lc($access) };
	    @realms = @_
	} else {
	    $access = "NetworkAccessPermission";
	}
    }

    printf "$amt_host  ";
    if ( !defined( $ENV{'AMT_NEW_PASSWORD'})  ) {
	printf "Error.  No password via AMT_NEW_PASSWORD environment variable.\n";
	return $pt_status{"PT_STATUS_INVALID_PASSWORD"};
    }
    if ( ! @realms ) {
	printf "Error.  No realms.\n";
	return -2;
    }

    @realms_arr = create_reals_arr( @realms );
    return -2 if (! @realms_arr);

    printf "  Add new user '%s' ", $name;
    return -1 if ( ! get_user_confirm() );

    my @args = prep_user_acl_entry( $amt2, $name, $access, @realms_arr );

    if ( $amt2 ) {
	return do_soap( $sas, "AddUserAclEntryEx",
			SOAP::Data->name( "EntryEx", \SOAP::Data->value(@args) ));
    } else {
	return do_soap( $sas, "AddUserAclEntry",
			SOAP::Data->name( "Entry",   \SOAP::Data->value(@args) ));
    }
}

#-------------------------------------------------------------------------------------
sub cmnd_user_update {
    my $cmd    = shift;
    my $name   = shift;
    my @realms = @_;
    my ( @realms_arr, $access, $ret );

    if ( $cmd eq "help" || !defined($name) ) {
	print_usage_header();
	printf "    user upd[ate] <username> [<access>] [<realms>]\n".
		"        Updates user's password and, if specified access and/or realms.\n".
		"        Password is passed via AMT_NEW_PASSWORD environment variable.\n";
	return 0;
    }

    if ( !defined( $ENV{'AMT_NEW_PASSWORD'}) ) {
	printf "Error.  No password via AMT_NEW_PASSWORD environment variable.\n";
	return $pt_status{"PT_STATUS_INVALID_PASSWORD"};
    }
#   return $pt_status{"PT_STATUS_INVALID_PASSWORD"}
#	if ( !check_password_strength( $ENV{'AMT_NEW_PASSWORD'}) );
#	printf "Error.  Password fails complexity check (should contain at least one symbol, digit, )";

    printf "$amt_host  ";

    my $amt2 = check_amt_version(2,0);
    if( @realms  &&  $amt2 ){
	$access = shift;
	$access = lc $access;
	if ( defined($access) &&
	     defined( $acl_access_permission{$access} )) {
	    @realms = @_;
	}
    }

    my $hndl = get_user_handle($name);
    if ( $hndl !~ m/[0-9]+/ ) {
	printf " Error.  Unknown username '%s'.\n", $name;
	return $pt_status{"PT_STATUS_INVALID_NAME"};
    }

    if( !@realms  ||
	$amt2 && !defined( $acl_access_permission{$access} )) {
	# get values of unchanged parameters
	my $arg = SOAP::Data->name( 'Handle' => $hndl );
	if ( $amt2 ) {
	    # AMT v. 2.x+
	    $ret = $sas->GetUserAclEntryEx( $arg )->paramsout;
	    $access = $ret->{'AccessPermission'}
		if !defined($access) || !defined( $acl_access_permission{$access} );
	} else {
	    # AMT 1.x
	    $ret = $sas->GetUserAclEntry( $arg )->paramsout;
	}
    }

    if ( @realms ) {
	@realms_arr = create_reals_arr( @realms );
	return $pt_status{"PT_STATUS_INVALID_REALM"}
		if (! @realms_arr);
    } else {
	foreach my $i ( $ret->{'Realms'}->{'Realm'} ) {
	    push( @realms_arr, SOAP::Data->name( "Realm" => $i ));
	};
    }

    printf "  Update user '%s' ", $name;
    return -1 if ( ! get_user_confirm() );

    my @args;
    my @entry = prep_user_acl_entry( $amt2, $name, $access, @realms_arr );
    push( @args, SOAP::Data->name( "Handle"  => $hndl ));

    if ( $amt2 ) {
	push( @args, SOAP::Data->name( "EntryEx",  \SOAP::Data->value(@entry) ));
	return do_soap( $sas, "UpdateUserAclEntryEx", @args );
    } else {
	push( @args, SOAP::Data->name( "Entry",  \SOAP::Data->value(@entry) ));
	return do_soap( $sas, "UpdateUserAclEntry", @args );
    }
}

#-------------------------------------------------------------------------------------
sub cmnd_user_del {
    my $cmd  = shift;
    my $name = shift;
    my ( $arg, $ret );

    $cmd = lc($cmd);

    if ( $cmd eq "help" || !defined($name)) {
	print_usage_header();
	printf "    user del <username>\n";
	return 0;
    }

    printf "$amt_host  ";

    my $hndl = get_user_handle($name);

    if ( $hndl !~ m/[0-9]+/ ) {
	printf " Error: unknown username '%s'.\n", $name;
	return $pt_status{"PT_STATUS_INVALID_NAME"};
    }

    printf "  DELETE user '%s' ", $name;
    return -1 if ( ! get_user_confirm() );

    $arg = SOAP::Data->name("Handle" => $hndl);
    return do_soap( $sas, "RemoveUserAclEntry", $arg );
}

#-------------------------------------------------------------------------------------
sub cmnd_user_info {
    my $cmd  = shift;
    my $name = shift;
    my ( $arg, $ret );

    $cmd = lc($cmd);

    if( $cmd eq "help" || !defined($name) ) {
	print_usage_header();
	printf "    user info <username>\n";
	return 0;
    }

    printf "$amt_host  ";

    my $hndl = get_user_handle($name);

    if ( $hndl !~ m/[0-9]+/ ) {
	printf " Error: unknown username '%s'.\n", $name;
	return $pt_status{"PT_STATUS_INVALID_NAME"};
    }

    printf "  User '%s' detailed info:\n", $name;

    my @realms = print_user_details( $hndl );
    printf "   Realms: ";
    do {
	my $i = pop @realms;
	printf "%s%s", $user_acl_realm{$i}, (@realms ? ", " : "\n\n");
    } while( @realms );

    return 0;
}

#-------------------------------------------------------------------------------------
sub cmnd_user_realms {
    my $cmd    = shift;

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    user realm[list]\n".
		"        Prints the user Access Control List realms.\n";
	return 0;
    }

    printf "\n User Access Control List realms:\n";

    for my $i ( 0 .. scalar( @user_acl_realm )-1 ) {
	printf "    %2d  ->  $user_acl_realm[$i]\n", $i;
    }
    printf "\n";
    return 0;
}

#-------------------------------------------------------------------------------------
sub cmnd_user {
    my @params = @_;
    my $cmd = shift;
    my $ret = -3;

    $cmd = lc($cmd);

    if( $cmd eq "help" || !@params ) {
	print_usage_header();
	$cmd = "help";
	@params = ( $cmd );
    }

    if ( $cmd =~ m/^(list|help)$/ ) {
	$ret = cmnd_user_list(@params);
    }

    if ( $cmd =~ m/^(info|help)$/ ) {
	$ret = cmnd_user_info(@params);
    }

    if ( $cmd =~ m/^(realm(list)?|help)$/ ) {
	$ret = cmnd_user_realms(@params);
    }

    if ( $cmd =~ m/^(add|help)$/ ) {
	$ret = cmnd_user_add(@params);
    }

    if ( $cmd =~ m/^(upd(ate)?|help)$/ ) {
	$ret = cmnd_user_update(@params);
    }

    if ( $cmd =~ m/^(del|help)$/ ) {
	$ret = cmnd_user_del(@params);
    }

    if ( $cmd =~ m/^((en|dis)(able)?|help)$/ ) {
	$ret = cmnd_user_enable(@params);
    }

    if ( $cmd =~ m/^(admin|help)$/ ) {
	$ret = cmnd_user_admin(@params);
    }

    if ( $cmd =~ m/^(adminupd(ate)?|help)$/ ) {
	$ret = cmnd_user_adminupd(@params);
    }

    if ($cmd eq "help") {
	print "\n";
    }

    if ($ret >= -2) {
	return $ret;
    } else {
	print " Unknown command: user $cmd\n";
	return 10;
    }
}
#=====================================================================================


#-------------------------------------------------------------------------------------
sub cmnd_hwasset_types {
    my $cmd = shift;
    my (@assets, $ret, $cnt);

    $cmd = lc($cmd);
    if ( $cmd eq "help") {
	print_usage_header();
	printf "    hwasset [list]\n".
		"        Enumerates the asset types supported by the AMT device.\n";
	return 0;
    }

    $ret = $has->EnumerateAssetTypes();

    if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	print_result($ret);
	return $ret->result;
    }

    $cnt  = ($ret->paramsout)[0];
    printf "AMT-Host '$amt_host'. Asset types supported (%d total):\n", $cnt;

    return if ( !$cnt );

    if ($cnt == 1) {
        @assets = ($ret->paramsout)[1]->{'AssetType'};
    } else {
        @assets = @{ ($ret->paramsout)[1]->{'AssetType'} };
    }

    foreach my $type ( @assets ) {
	if( !defined($hwasset_type_names{$type}) ) {
	    printf "  unknown asset code (%d).\n", $type;
	} else {
	    printf "  %s\n", $hwasset_type_names{$type};
	}
    }
    printf "\n";
    return 0;
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print {
    my $cmd = shift;
    my @params = @_;
    my $all = 0;
    my (@ret, @data, $ret, $cnt, $arg, $type_code);

    $cmd = lc($cmd);

    if ( $cmd eq "help") {
	print_usage_header();
	printf "    hwasset data <asset_type>\n".
		"        Prints hardware asset data for a given asset type.\n".
		"        <asset_type> - case insensitive, supported by the device ('hwasset list') or 'All'.\n\n";
	return 0;
    }

    printf "AMT-Host '$amt_host'.\n";

    if( lc($params[0]) eq "all" ) {
	$all = 1;
	@params = ();
	push @params, ( sort keys %hwasset_type_codes );
    }

    foreach my $type ( @params ) {

	$type_code = $hwasset_type_codes{ lc($type) };
	if( !defined( $type_code )) {
	    printf " Unknown asset type '$type' given.\n";
	    next;
	}
	$type = $hwasset_type_names{ $type_code };

	$arg = SOAP::Data->name( 'AssetType' => $type_code );
	$ret = $has->GetAssetData( $arg );

	if( !defined($ret->result) ) {
	    printf " Warning. Requested asset type '$type' is not supported.\n" if( !$all );
	    next;
	}
	printf " Data for the asset '$type' ";

	if ( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	    if( $ret->result == $pt_status{'PT_STATUS_MAX_LIMIT_REACHED'} ) {
		printf " -- Warning.  The platform contains additional asset data information that\n".
			"\twas not captured by the Intel AMT device due to container space limitation.\n";
		next;
	    }
	    elsif( $ret->result == $pt_status{'PT_STATUS_INVALID_PARAMETER'} ) {
		printf " -- Warning. Asset type is not supported.\n" if( !$all );
		next;
	    }
	    else {
		print_result($ret);
		return $ret->result;
	    }
	}
	$cnt = ($ret->paramsout)[0];
	if( $cnt ) {
	    printf "(%d item%s):\n", $cnt, ($cnt-1 ? "s" : "");
	} else {
	    printf "isn't supported by the device.\n\n";
	    next;
	}

	my $asset_data = (($ret->paramsout)[1])->{'AssetData'};
	foreach my $item ( ($cnt>1 ? @{ $asset_data} : $asset_data) ) {
	    if( $param_hex ) {
		my $t = $item->{'AssetSize'};
		printf "  Data size, bytes: %d\n  Asset data:", $t;
		foreach my $byte ( unpack "C[$t]", decode_base64( $item->{'AssetData'} )) {
		    printf " %02x", $byte;
		}
		printf "\n";
	    } else {
		cmnd_hwasset_print_bios( $item ) if( $hwasset_type_codes{'bios'} == $type_code );
		cmnd_hwasset_print_computer( $item ) if( $hwasset_type_codes{'computersystem'} == $type_code );
		cmnd_hwasset_print_baseboard( $item ) if( $hwasset_type_codes{'baseboard'} == $type_code );
		cmnd_hwasset_print_processor( $item ) if( $hwasset_type_codes{'processor'} == $type_code );
		cmnd_hwasset_print_memory( $item ) if( $hwasset_type_codes{'memorymodule'} == $type_code );
		cmnd_hwasset_print_fru( $item ) if( $hwasset_type_codes{'fru'} == $type_code );
		cmnd_hwasset_print_mediadevice( $item ) if( $hwasset_type_codes{'mediadevice'} == $type_code );
		cmnd_hwasset_print_port_batt( $item ) if( $hwasset_type_codes{'portablebattery'} == $type_code );
		cmnd_hwasset_print_vpro( $item ) if( $hwasset_type_codes{'vproverificationtable'} == $type_code );
		cmnd_hwasset_print_amt( $item ) if( $hwasset_type_codes{'amtinformation'} == $type_code );
	    }
	}
    printf "\n";
    }
    return 0;
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_bios {
    my @data = @_;

#   if( $data[0]->{'AssetSize'} != 204 ) {
#	printf "  Error: Wrong data size in the device reply (%d bytes, should be 204).", $data[0]->{'AssetSize'};
#   };

    my ( $struct_ver, $vendor, $ver, $rel_date, $padd, $charact ) =
	unpack "L Z65 Z65 Z65 C L", decode_base64( $data[0]->{'AssetData'} );
    if( $padd != 0 ) {
	printf "   Error. Something's going wrong, received data has failed checking.\n";
	return 15;
    }

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   Vendor:\t '$vendor'\n";
    printf "   Version:\t '$ver'\n";
    printf "   Release date: '$rel_date'\n";
    printf "   BIOS characteristics: ";
    foreach my $i ( sort {$a <=> $b} keys %hwasset_bios_characteristics ) {
	printf "'".$hwasset_bios_characteristics{$i}."' " if( $charact & $i );
    }
    printf "\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_computer {
    my @data = @_;

    my ( $struct_ver, $manufact, $product, $ver, $ser_num, $uuid1, @uuid ) =
	unpack "L Z65 Z65 Z65 Z65 L S[2] H[4] H[12]", decode_base64( $data[0]->{'AssetData'} );

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   Manufacturer: '$manufact'\n";
    printf "   Product:\t '$product'\n";
    printf "   Version:\t '$ver'\n";
    printf "   Serial numb.: '$ser_num'\n";
    printf "   UUID:\t %x-%x-%x-%s-%s\n", uc($uuid1), $uuid[0], $uuid[1], $uuid[2], $uuid[3], $uuid[4];
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_baseboard {
    my @data = @_;

    my ( $struct_ver, $manufact, $product, $ver, $ser_num, $a_tag, $repl, $padd ) =
	unpack "L Z65 Z65 Z65 Z65 Z65 C S", decode_base64( $data[0]->{'AssetData'} );

    if( $padd != 0 ) {
	printf "   Error. Something's going wrong, received data has failed checking.\n";
	return 15;
    }
    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   Manufacturer: '$manufact'\n";
    printf "   Product:\t '$product'\n";
    printf "   Version:\t '$ver'\n";
    printf "   Serial numb.: '$ser_num'\n";
    printf "   Asset tag:\t '$a_tag'\n";
    printf "   Replaceable:  ".($repl ? "yes" : "no")."\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_processor {
    my @data = @_;

    my ( $struct_ver, $id1, $id2, $max_speed, $speed, $status, $type, $fam,
	$upg_info, $socket_pop, $socket_desig, $manufact, $ver ) =
	unpack "L L L S S C C C C C Z65 Z65 Z65", decode_base64( $data[0]->{'AssetData'} );

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   ID:\t\t\t0x%08x%08x\n", $id2, $id1;
    printf "   Max Socket Speed:\t$max_speed MHz\n";
    printf "   Current Speed:\t$speed MHz\n";

    printf "   Processor Status:\t".
		($status == 0x0 ? "Unknown" : "").
		($status == 0x1 ? "Enabled" : "").
		($status == 0x2 ? "Disabled by user" : "").
		($status == 0x3 ? "Disabled by BIOS" : "").
		($status == 0x4 ? "Waiting to be enabled" : "").
		($status == 0x7 ? "Other" : "").
		"\n";
    printf "   Processor Type:\t".
		($type == 0x1 ? "Other" : "").
		($type == 0x2 ? "Unknown" : "").
		($type == 0x3 ? "Central" : "").
		($type == 0x4 ? "Math" : "").
		($type == 0x5 ? "DSP" : "").
		($type == 0x6 ? "Video" : "").
		"\n";
    printf "   Socket Populated:\t".($socket_pop ? "yes" : "no")."\n";
    printf "   Processor family:\t";
    defined( $hwasset_cpu_family{$fam}) ? printf "'%s'", $hwasset_cpu_family{$fam} : printf "[0x%x]", $fam;

    printf "\n   Upgrade Information:\t";
    defined( $hwasset_cpu_upg_info{$upg_info}) ? printf "'%s'", $hwasset_cpu_upg_info{$upg_info} : printf "[0x%x]", $upg_info;

    printf "\n   Socket Designation:  '$socket_desig'\n";
    printf "   Manufacturer:\t'$manufact'\n";
    printf "   Version:\t\t'$ver'\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_memory {
    my @data = @_;

    my ( $struct_ver, $size, $f_fact, $type, $type_det, $speed, $manufact, $ser_num, $a_tag, $part_num ) =
	unpack "L S C C S S Z65 Z65 Z65 Z65", decode_base64( $data[0]->{'AssetData'} );

    if( $size == 0 ) {
	printf "  (* No memory device in the socket *)\n";
	return;
    }

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   Size:\t ";
    if( $size == 0xFFFF) {
	printf "Unknown\n";
    } else {
	if( $size & 0x8000 ) {
	    printf "%d Kb\n", ($size & 0x7FFF);
	} else {
	    printf "$size Mb\n";
	}
    }
    printf "   Form Factor:  ";
    defined( $hwasset_mem_form_fact{$f_fact}) ? printf "'%s'", $hwasset_mem_form_fact{$f_fact} : printf "[0x%x]", $f_fact;

    printf "\n   Memory Type:  ";
    defined( $hwasset_mem_type{$type}) ? printf "'%s'", $hwasset_mem_type{$type} : printf "[0x%x]", $type;

    printf "\n   Memory Type Details:";
    my $div='';
    foreach my $i ( sort {$a <=> $b} keys %hwasset_mem_type_det ) {
	printf $div." '".$hwasset_mem_type_det{$i}."'" if( $type_det & $i );
	$div = ',';
    }

    printf "\n   Speed:\t ".($speed ? $speed." MHz" : "unknown")."\n";
    printf "   Manufacturer: '$manufact'\n";
    printf "   Serial numb.: '$ser_num'\n";
    printf "   Asset Tag:\t '$a_tag'\n";
    printf "   Part Number:\t '$part_num'\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_fru {
    my @data = @_;

    my( $struct_ver, $vend_id, $dev_id, $rev_id, $prog_if, $sub_cl,
	$base_cl, $sub_vend_id, $subsyst_id, $dev_loc, $padd ) =
	unpack "L S S C C C C S S S S", decode_base64( $data[0]->{'AssetData'} );

    if( $padd != 0 ) {
	printf "   Error. Something's going wrong, received data has failed checking.\n";
	return 15;
    }

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;

    printf "   Vendor ID:\t0x%x\n", $vend_id;
    printf "   Device ID:\t0x%x\n", $dev_id;
    printf "   RevisionID:\t0x%x\n", $rev_id;
    printf "   Progr.Iface: 0x%x\n", $prog_if;
    printf "   Subclass:\t0x%x\n", $sub_cl;
    printf "   Base Class:\t0x%x\n", $base_cl;
    printf "   SubVendorID: 0x%x\n", $sub_vend_id;
    printf "   SubSystemID: 0x%x\n", $subsyst_id;
    printf "   DevLocation: 0x%x\n", $dev_loc;
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_mediadevice {
    my @data = @_;

    my ( $struct_ver, $model, $serial, $cap84, $cap83, $cap82, $max_size0, $max_size1, $padd ) =
	unpack "L Z40 Z20 S3 L2 S", decode_base64( $data[0]->{'AssetData'} );

    if( $padd != 0 ) {
	printf "   Error. Something's going wrong, received data has failed checking.\n";
	return 15;
    }
    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   Model Number:  '$model'\n";
    printf "   Serial Number: '$serial'\n";
    printf "   Max MediaSize: ".(Math::BigInt->new($max_size1)->blsft(32)->badd($max_size0))." bytes\n";

    printf "   Capabilities:";
    my $div='';
    foreach my $i ( sort {$a <=> $b} keys %hwasset_media_cap82 ) {
	printf $div." '".$hwasset_media_cap82{$i}."'" if( $cap82 & $i );
	$div = ',';
    }
    $div='';
    foreach my $i ( sort {$a <=> $b} keys %hwasset_media_cap83 ) {
	printf $div." '".$hwasset_media_cap83{$i}."'" if( $cap83 & $i );
	$div = ',';
    }
    $div='';
    foreach my $i ( sort {$a <=> $b} keys %hwasset_media_cap84 ) {
	printf $div." '".$hwasset_media_cap84{$i}."'" if( $cap84 & $i );
	$div = ',';
    }
    printf "\n";
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_port_batt {
    my @data = @_;

    my ( $struct_ver, $oem, $sbds_date, $des_cap, $des_volt, $sbds_serial, $des_cap_mltpl, $dev_chem,
	 $max_err, $serial, $sbds_ver, $locat, $manufact, $man_date, $dev_name, $sbds_dev_chem ) =
	unpack "L L L S S S C C C Z65 Z65 Z65 Z65 Z65 Z65 Z65", decode_base64( $data[0]->{'AssetData'} );

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   OEM Specific Info: 0x%x\n", $oem;
    printf "   Design Capacity:  ".($des_cap ? ($des_cap*$des_cap_mltpl)." mWatt-hours" : "unknown")."\n";
    printf "   Design Voltage:   ".($des_volt ? $des_volt." mVolts" : "unknown")."\n";

    printf "   Device Chemistry:  ";
    if( $dev_chem == 0x02 ) {
	printf $sbds_dev_chem;
    } else {
	printf  ($dev_chem==0x01 ? "Other" : "" ).
		($dev_chem==0x03 ? "Lead Acid" : "" ).
		($dev_chem==0x04 ? "Nickel Cadmium" : "" ).
		($dev_chem==0x05 ? "Nickel metal hydride" : "" ).
		($dev_chem==0x06 ? "Lithium-ion" : "" ).
		($dev_chem==0x07 ? "Zinc air" : "" ).
		($dev_chem==0x08 ? "Lithium Polymer" : "" );
    }

    printf "\n   Max Error In Battery Data: $max_err\%\n";
    printf "   Location:         '$locat'\n";

    printf "   Manufacturer:     '$manufact'\n";
    printf "   Device Name:      '$dev_name'\n";
    printf "   Manufacture Date: ";
    if( defined($man_date) ) {
	printf "'$man_date'\n";
    } else {
	printf "%4d/%02d/%02d (YYYY/MM/DD)\n", (($sbds_date&0xFE00) >>9)+ 1980, ($sbds_date&0x1E0)>>5, $sbds_date&0x1F;
    }
    printf "   SerialNumber:     ";
    if( defined($serial) ) {
	printf "'$serial'\n";
    } else {
	printf "0x%x\n", $sbds_serial;
    }
    printf "   SBDS Ver. Number: '$sbds_ver'\n";

}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_vpro {
    my @data = @_;

    my ( $struct_ver, $cpu, $mch0, $mch1, $ich0, $ich1, $me0, $me1, $me2,
	 $tpm, $net0, $net1, $net2, $bios, $struct_id, $rsrvd  ) =
	unpack "L16", decode_base64( $data[0]->{'AssetData'} );

    if( $struct_id != 0x6F725076 ) {
	printf "  Received data check failed (wrong StructureIdentifier field).\n";
	return 22;
    }

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;

    printf "   CPU:".
	" VMX=".($cpu & 0x1 ? "Enabled" : "Disabled").
	" SMX=".($cpu & 0x2 ? "Enabled" : "Disabled").
	" LT/TXT=";
    if( $cpu & 0x4 ) {
	printf ($cpu & 0x8 ? "Enabled" : "Disabled");
    }else{
	printf "NOT_Capable";
    }

    printf " VT-x=";
    if( $cpu & 0x10 ) {
	printf ($cpu & 0x20 ? "Enabled" : "Disabled");
    }else{
	printf "NOT_Capable";
    }
    my $mask = 0x40;
    for my $i ( 6..31 ) {
	printf " Bit_".$i."=Enabled" if ($cpu & $mask);
	$mask <<= 1;
    }

    printf "\n   MCH:".
	 "\tPCI Bus 0x%02x / Dev 0x%02x / Func 0x%02x\n", $mch0 & 0xFF00, $mch0 & 0xF8, $mch0 & 0x7;
    printf "\tDev Identification Number (DID): 0x%04x\n", ($mch0 & 0xFFFF0000) >>16;
    printf "\tCapabilities: VT-d=";
    if( $mch1 & 0x1 ) {
	printf ($mch1 & 0x2 ? "Enabled" : "Disabled");
    }else{
	printf "NOT_Capable";
    }
    printf " TXT=";
    if( $mch1 & 0x1 ) {
	printf ($mch1 & 0x2 ? "Enabled" : "Disabled");
    }else{
	printf "NOT_Capable";
    }
    $mask = 0x4;
    for my $i ( 36..63 ) {
	printf " Bit_".$i."=Enabled" if ($mch1 & $mask);
	$mask <<= 1;
    }

    printf "\n   ICH:".
	 "\tPCI Bus 0x%02x / Dev 0x%02x / Func 0x%02x\n", $ich0 & 0xFF00, $ich0 & 0xF8, $ich0 & 0x7;
    printf "\tDev Identification Number (DID): 0x%04x\n", ($ich0 & 0xFFFF0000) >>16;
#    $mask = 0x1;
#    for my $i ( 32..63 ) {
#	printf " Bit_".$i."=Enabled" if ($ich1 & $mask);
#	$mask <<= 1;
#    }

    printf "   ME:  ".($me0 & 0x1 ? "Enabled" : "Disabled").
	"\n\tIntel_QST_FW=".($me0 & 0x2 ? "" : "NOT_")."Supported".
	" Intel_ASF_FW=".($me0 & 0x4 ? "" : "NOT_")."Supported".
	" Intel_AMT_FW=".($me0 & 0x8 ? "" : "NOT_")."Supported";
    my $div="\n\t";
    $mask = 0x10;
    for my $i ( 4..31 ) {
	printf $div."Bit_".$i."=Enabled" if ($me0 & $mask);
	$mask <<= 1;
	$div=" ";
    }
    printf "\n\tME FW ver. %d.%d hotfix %d build %d\n",
	($me1&0xFFFF0000) >>16, $me1&0xFFFF, ($me2&0xFFFF0000) >>16, $me2&0xFFFF;

    printf "   TPM: ".($tpm & 0x2 ? "Enabled" : "Disabled").
	"\n\tTPM on board = ".($tpm & 0x1 ? "" : "NOT_")."Supported";
    if( $tpm & 0x1 ) {
	$div="\n\tReserved bits: ";
	$mask = 0x4;
	for my $i ( 2..15 ) {
	    printf $div."Bit_".$i."=Enabled" if ($tpm & $mask);
	    $mask <<= 1;
	    $div=" ";
	}
	printf "\n\tTPM designed to TCG spec ver. %d.%d",
	    ($tpm&0xFF0000) >>16, ($tpm&0xFF000000) >>24;
    }

    printf "\n   Network Devices:\n".
	 "\tWired NIC - PCI Bus 0x%02x / Dev 0x%02x / Func 0x%02x / ", $net0 & 0xFF00, $net0 & 0xF8, $net0 & 0x7;
    printf "DID 0x%04x\n", ($net0 & 0xFFFF0000) >>16;

    printf "   BIOS";
    $div=" ";
    if( $bios & 0x1F ) {
	printf " supports setup screen for (can be editable):".
	    ($bios & 0x1 ? " VT-x" : "").
	    ($bios & 0x2 ? " VT-d" : "").
	    ($bios & 0x4 ? " TXT" : "").
	    ($bios & 0x8 ? " TXT" : "").
	    ($bios & 0x10 ? " ME" : "")."\n";
	$div = "\t";
    }
    if( $bios & 0x20 ) {
	my $t = ($bios&0x380) >>7;
	printf $div."supports VA extensions (ACPI Op region) with maximum ver. ".
	    ($t == 0 ? "2.6" : "").
	    ($t == 1 ? "3.0" : "").
	    ($t >  1 ? "higher than 3.0" : "")."\n";
    }
    printf $div."SPI Flash has Platform Data region reserved.\n" if( $bios & 0x40 );
    printf $div."(There is data in reserved bits).\n" if( $bios & 0xFFFFFE00 );

    printf "   Reserved 4 bytes (for future expansion) contains data.\n" if( $rsrvd );
}

#-------------------------------------------------------------------------------------
sub cmnd_hwasset_print_amt {
    my @data = @_;

    my ( $struct_ver, $supp, $enab, $ider, $sol, $net, $ext_data, $oem0, $oem1, $oem2, $oem3, $rsrvd ) =
	unpack "L C10 S", decode_base64( $data[0]->{'AssetData'} );

    printf "  (data struct.ver. %d.%d)\n", $struct_ver & 0xFFFF, $struct_ver >> 16;
    printf "   AMT: ";
    if( $supp == 0 ) {
	printf "NOT ";
#	return;
    }
    printf "Supported, ".($enab == 1 ? "Enabled" : "Disabled")."\n";
    printf "   IDE-R: ".($ider == 1 ? "Enabled" : "Disabled")."\n";
    printf "   SOL: ".($sol == 1 ? "Enabled" : "Disabled")."\n";
    printf "   Network: ".($net == 1 ? "Enabled" : "Disabled")."\n";
    printf "   OEM capabilities:\n\tOEM-Specific data: 0x%02x\n", $ext_data;
    printf "\tSupports:".
	    ($oem0 & 0x1  ? " IDE-R" : "").
	    ($oem0 & 0x2  ? " SOL" : "").
	    ($oem0 & 0x4  ? " BiosReflash" : "").
	    ($oem0 & 0x8  ? " BiosSetupScreen" : "").
	    ($oem0 & 0x10 ? " BiosPause" : "").
	    ($oem0 & 0x20 ? " No": " ")."FloppyBoot".
	    ($oem0 & 0x40 ? " No": " ")."CDBoot".
	    ($oem0 & 0x80 ? " ReservedBit": "")."\n";
    printf "\tTerminal Emulation: ".
	    ($oem1 == 1  ? "VT52 (Basic monochrome)" : "").
	    ($oem1 == 2  ? "VT100+ (Adds function keys F5 to F14)" : "").
	    ($oem1 == 3  ? "VT-UTF8" : "").
	    ($oem1 == 4  ? "PC-ANSI" : "").
	    ($oem1  > 4  ? "unknown type" : "")."\n";
    printf "\t3rd Byte: 0x%02x\n", $oem2;
    printf "\t4th Byte: 0x%02x\n", $oem3;
    printf "   Reserved Bytes: 0x%04x\n", $rsrvd if( $rsrvd );
}


#-------------------------------------------------------------------------------------
sub cmnd_hwasset {
    my @params = @_;
    my $cmd = shift;
    my $ret = -3;

    $cmd = lc($cmd);

    if ($cmd eq "help" ) {
	print_usage_header();
	$cmd = "help";
	@params = ( "help" );
    }

    if ( $cmd =~ m/^((list)?|help)$/ ) {
	$ret = cmnd_hwasset_types(@params);
    }
    if ( $cmd =~ m/^(data|help)$/ ) {
	$ret = cmnd_hwasset_print(@params);
    }


    if ($ret >= -2) {
	return $ret;
    }
    else {
	print " Unknown command: hwasset $cmd\n";
	return 10;
    }
}
#=====================================================================================


#-----------------------------------------------------------------------------------------
sub cmnd_audit_info {
    my $cmd = shift;
    my (@ret, $ret, $cnt, $arg, $a_id, $e_id, $f_id, $app_id, $n);

    if ( lc($cmd) eq "help") {
	printf "    audit [info]\n        general audit information, synonim for 'info audit'\n";
	return 0;
    }

    return -22 if ( !check_amt_version(4,0, "  Audit not supported") );

    @ret = $gis->GetAuditPolicy->paramsout->{'AuditPolicyRecords'};

    printf "\n Audit Policy:\n   ";
    $cnt = scalar(@{$ret[0]}) - 1;

    # log header
    foreach my $item (sort keys %{$ret[0][0]}){
	$n = 0;
	if( $item =~ m/AppId/i ) {
	    $a_id = $item;
	    $n = 25;
	} elsif( $item =~ m/EventId/i ) {
	    $e_id = $item;
	    $n = 38;
	} elsif( $item =~ m/flag/i ) {
	    $f_id = $item;
	}
	printf "%-*s".($n ? "\t" : ""), ($param_num ? 4 : $n), $item;
    }
    print "\n";

    # audit_log rows
    for my $i ( 0 .. $cnt ) {
	print "  ";
	$n = 0;
	foreach my $item (sort keys %{$ret[0][$i]}) {
	    my $val = $ret[0][$i]->{$item};
	    if( $param_num ) {
		printf "".($n ? "\t":"     ")."%s", $val;
		$n = 1;
		next;
	    }
	    if( $a_id eq $item  &&  defined($auditable_app_id{$val}) ) {
		printf "  %-24s\t", $auditable_app_id{$val};
		$app_id= $val;
		next;
	    }
	    if( $e_id eq $item  &&  defined($audit_event_id{$app_id}{$val}) ) {
		printf "%-38s", $audit_event_id{$app_id}{$val};
		next;
	    }
	    if( $f_id eq $item ) {
		printf "\t".( $val == 0 ? "-" : "" ).
			    ( $val == 1 ? "Critical" : "" ).
			    ( $val >  1 ? "Unknown [$val]" : "" );
		next;
	    }
	    printf "\t%s", $val;
	}
	print "\n";
    }

    @ret = $gis->GetAuditLogStatus()->paramsout;
    printf "\n Audit Log Status:\n";
	printf "   PercentageFree:\t".$ret[0];
	printf "\n   NumberOfRecords:\t".$ret[1];
	printf "\n   TimeOfLastRecord:\t";
	print_date_time( $ret[2], 0 );
	printf "\n   State:\t\t".
		($ret[3] & 0x01 ? "Disabled " : "").
		($ret[3] & 0x02 ? "Locked " : "").
		($ret[3] & 0x04 ? "Almost_Full " : "").
		($ret[3] & 0x08 ? "FULL! " : "").
		($ret[3] & 0x10 ? "NoKey" : "").
		($ret[3] ==   0 ? "Enabled" : "");
	printf "\n   MaxAllowedAuditors:\t".$ret[4]."\n\n";
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_log {
    my ($cmd, $filt_app, $filt_evnt ) = @_;

    if ( lc($cmd) eq "help" || (defined($filt_app) && ($filt_app !~ m/^[0-9]+$/)) ||
	 (defined($filt_evnt) && ($filt_evnt !~ m/^[0-9]+$/)) ) {
	printf "    audit log [<audited_app_id> [<event_id]]\n".
		"\tprints logged events, <IDs> - numeric, optional, to filter log output\n".
		"\tExtendedData is dumped only in numeric mode\n";
	return 0;
    }

    my( $app_id, $ev_id, $init_type, $init_data, $time,	$mc_loc, $naddr_len, $naddr, $edata_len, @edata,
	$len, @tmp, $str, $ret, $cnt, $arg);

    my $got_cnt = 0;
    my $tot_cnt = 1;

    while( $got_cnt < $tot_cnt ) {
	$arg = SOAP::Data->name( 'StartIndex' => $got_cnt+1 );
	$ret = $gis->ReadAuditLogRecords( $arg );
	return $ret->result if( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} );

	$tot_cnt = $ret->valueof( '//TotalRecordCount' );
	$got_cnt += $tot_cnt;

	printf "\n Audit Log contains ".$tot_cnt." record".($tot_cnt != 1 ? "s" : "").".\n"
	    if( !$got_cnt );
	return 0 if( !$tot_cnt );

	if( !$got_cnt && !$param_hex ) {
	    printf " Log filtered with: AuditedAppID=$filt_app".
		    ( defined($filt_evnt) ? ", EventID=$filt_evnt" : "" )."\n\n"
		if( defined($filt_app) );

	    printf " %-*s\t%-*s\tInitiator\tTime            ".
		    "\tMCLocType\tNetAddress".($param_num ? "\tExtendedData\n" : "\n"),
		    ($param_num ? 1 : 24), ($param_num ? "AuditedAppID" : "Auditable Application" ),
		    ($param_num ? 1 : 38), "Event".($param_num ? "ID" : "" );
	}

	foreach my $row ( $ret->valueof( '//EventRecords' ) )
	{
	    my $log_str = decode_base64( $row );
	    if( $param_hex ) {
		foreach my $byte ( unpack "C255",$log_str ) {
		    printf " %02x", $byte;
		}
		    print "\n";
		next;
	    }

	    ( $app_id, $ev_id, $init_type, @tmp ) = unpack "n n C6", $log_str;

	    if( defined($filt_app) ) {
		next if( $filt_app != $app_id ||
			( defined($filt_evnt) && $filt_evnt != $ev_id) );
	    }

	    printf " %-*s\t%-*s\t",
		($param_num ? 7 : 24), (defined($auditable_app_id{$app_id}) && !$param_num ?
					$auditable_app_id{$app_id} : $app_id),
		($param_num ? 2 : 38), (defined( $audit_event_id{$app_id}{$ev_id}) && !$param_num ?
					$audit_event_id{$app_id}{$ev_id} : $ev_id);
	    if( $init_type == 0) {
		$len = $tmp[0];
		( $init_type, $str ) = unpack "a6 Z[$len]", $log_str;
		$len++;
	    } elsif( $init_type == 1) {
		$str = "<Kerberos SID>";
		$len = 5 + $tmp[4];
	    } else {
		$str = "Local";
		$len = 0;
	    }
	    printf "%-9s\t", $str;
	    $len += 5;

	    ( $init_type, $time, $mc_loc, $naddr_len, $edata_len ) =
		unpack "a[$len] N C3", $log_str;
	    print_date_time $time, 0;
	    printf "\t%-8s\t", ($mc_loc==0 ? "IPv4" : "").
		    ($mc_loc==1 ? "IPv6" : "").($mc_loc==2 ? "None" : "");
	    $len += 6;

	    if( $naddr_len ) {
		( $init_type, $naddr, $edata_len ) = unpack "a[$len] Z[$naddr_len] C", $log_str;
		printf "%-10s", ($naddr_len ? $naddr : "");
	    }

	    if( $param_num ) {
		printf "\t";

		if( $edata_len ) {
		    $len += $naddr_len + 1;
		    ( $init_type, @edata ) = unpack "a[$len] a[$edata_len]", $log_str;
		    for my $b ( 0 .. scalar(@edata)-1 ) {
			printf " %02x", $edata[$b];
		    }
		} else {
		    printf "<none>" }
	    }
	    printf "\n";
	}
    }
    printf "\n";
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_idlist {
    my $cmd = shift;

    if ( lc($cmd) eq "help") {
	printf "    audit id[list]\n\tlist of known event names & IDs\n";
	return 0;
    }

    printf "  %-30s\tAuditedAppID\tEventID\n", "Event Name";

    foreach my $app_id (sort {$a <=> $b} keys %audit_event_id ) {
	printf " = %s =\n", $auditable_app_id{$app_id};
	foreach my $ev_id ( sort {$a <=> $b} keys %{ $audit_event_id{$app_id} } ) {
		printf "  %-38s\t%d\t%d\n", $audit_event_id{$app_id}{$ev_id}, $app_id, $ev_id;
	}
    }
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_enable {
    my ( $cmd, $state ) = @_;
    $state = lc($state);

    if ( lc($cmd) eq "help" || !defined($state) ||
	 ($state ne "true" && $state ne "false" )) {
	printf "    audit enable <state>\n\tenables/disables audit; <state> may be (true|false)\n";
	return 0;
    }

    $state = ($state eq "true" ? 1 : 0 );

    printf "  The audit log cannot be enabled prior to setting the signature key and certificate.\n" if( $state );
    printf "  Setting Audit State to '".( $state ? "Enabled" : "Disabled" )."': ";
    return -1 if ( ! get_user_confirm() );

    my $arg = SOAP::Data->name( 'Enable' => $state );
    return do_soap $als, "EnableAuditing", $arg;
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_clear_log {
    my $cmd = shift;

    if ( lc($cmd) eq "help" ) {
	printf "    audit clear[log]\n\tclears the contents of the audit log\n";
	return 0;
    }

    printf "  May only be called when the audit log service is in a LOCKED STATE!\n\n".
	    "  Clear the Audit Log: ";
    return -1 if ( ! get_user_confirm() );

    return do_soap $als, "ClearAuditLog";
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_set_policy {
    my ( $cmd, $state, $app_id, $evnt_id, $flag ) = @_;
    $state = lc( $state );
    $flag = lc( $flag );

    if( (lc($cmd) eq "help") || #!defined($state) || !defined($app_id) || !defined($evnt_id) ||
	( $state ne "enable" && $state ne "disable") ||
	( $app_id !~ m/^[0-9]+$/) || ($evnt_id !~ m/^[0-9]+$/) ||
	( $flag ne "critical" && $flag ne "") ) {
	printf "    audit policy <state> <audAppId> <eventId> [<flag>]\n".
		"\tset a new audit policy\n".
		"\t<state>=enable,disable. IDs=numeric values. <flag>=critical\n";
	return 0;
    }

    printf " Set the Audit log Policy to < $state $app_id-$evnt_id >".
	    ($flag ne "" ? " $flag: " : ": ");
    return -1 if ( ! get_user_confirm() );

    my ( @arg, @policy );

    push @policy, SOAP::Data->name( 'AuditedAppID' => $app_id );
    push @policy, SOAP::Data->name( 'EventId'      => $evnt_id );
    push @policy, SOAP::Data->name( 'Flag'         => ($flag eq "critical" ? 1 : 0) );

    push @arg, SOAP::Data->name( 'Enable' => ($state eq "enable" ? 1 : 0 ) );
    push @arg, SOAP::Data->name( 'AuditPolicy' => \SOAP::Data->value(@policy) );

    return do_soap $als, "SetAuditPolicy", @arg;
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_storage {
    my ( $cmd, $set, $policy, $days ) = @_;
    my ( $ret );
    $set = lc( $set );
    $policy = lc( $policy );
    my $pol_r = $policy eq 'restrict_ro';

    if( (lc($cmd) eq "help") || ( $set ne "set" && $set ne "") ||
	( $set eq "set" && ( !defined $audit_storage_policy_set{ $policy } ||
	  ( $pol_r && (!defined($days) || $days < 1 || $days > 100) ) )) ) {
	printf "    audit storage [<set> <policy> [<days>]]\n".
		"\tget/set the audit log records storage policy (AMT 5.1+)\n".
		"\t<policy>=rollover,no_ro,restrict_ro. <days>=1-100 (restricted rollover only)\n";
	return 0;
    }

    return -2 if ( !check_amt_version(5,1, "  AuditLogStoragePolicy doen't supported") );

    if( $set ne "set" ) {
	$ret = $als->GetAuditLogStoragePolicy;
	return $ret->result if( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} );

	my @po = $ret->paramsout;
	printf "  Audit Log Storage policy in use: %s\n", $audit_storage_policy_get{ $po[0] };
	printf "  Minimal days to keep any record: %d\n", $po[1]
	    if( $po[0] == $audit_storage_policy_set{ 'restrict_ro' } );
	printf "\n";
	return 0;
    }

    my $pol_code = $audit_storage_policy_set{ $policy };
    printf "  Set the Audit Log Storage Policy to '".$audit_storage_policy_get{$pol_code}."'".
	($pol_r ? " with minimum $days day".($days==1 ? "":"s")." to keep: " : ": ");
    return -1 if ( ! get_user_confirm() );

    my @arg;
    push @arg, SOAP::Data->name( 'StoragePolicy' => $pol_code );
    push @arg, SOAP::Data->name( 'DaysToKeep'    => $days )
	if( $pol_r );

    return do_soap $als, "SetAuditLogStoragePolicy", @arg;
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_signature {
    my $cmd = shift;

    if ( lc($cmd) eq "help" ) {
	printf "    audit sign[ature]\n\treturns the audit log signature and related info\n";
	return 0;
    }

    printf "  Audit Log Signature and Log related information\n";

    my $arg = SOAP::Data->name( 'SigningMechanism' => 0 );
    my $ret = $als->ExportAuditLogSignature( $arg );
    if( $ret->result != $pt_status{'PT_STATUS_SUCCESS'} ) {
	print "    Check is Audit Log Enabled.\n    ";
	print_result $ret;
	print "\n";
	return $ret->result;
    }

    my $sign = $ret->valueof('//AuditSignature');

    print "    Audit Log Records:\t ", $sign->{'TotalRecordCount'}, "\n",
	"    Start Log Time:\t ";
    print_date_time $sign->{'StartLogTime'}, 0;
    print "\n    End Log Time:\t ";
    print_date_time $sign->{'EndLogTime'}, 0;

    print "\n    System UUID:\t ";
    print_uuid $sign->{'UUID'};
    print "    System FQDN:\t ", $sign->{'FQDN'};

    print "\n    Signature generated: ";
    print_date_time $sign->{'GenerationTime'}, 0;

    print "\n    Signature Mechanism: ", $signing_mechanism{ $sign->{'SignatureMechanism'} },
	    "\n    Signature Data (BASE64 encoded):\n\t",
	    join( "\n\t", split( '\n', encode_base64( $sign->{'Signature'} )) );

    my @len = ( @{ $sign->{'LengthOfCertificates'} }, 0, 0, 0, 0, 0 );
    my @certs = unpack "a[$len[0]] a[$len[1]] a[$len[2]] a[$len[3]] a[$len[4]]",
			$sign->{'Certificates'};
    foreach my $c ( @certs ) {
	next if( $c eq "" );
	print "\n    Certificate Data (BASE64 encoded):\n\t",
        join( "\n\t", split( '\n', encode_base64( $c )) );

    }
    printf "\n\n";
}

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub sub_audit_parse_pem {
    my ( $fname, $beg_str, $end_str ) = @_;
    my $buf;

    open( FILE, $fname ) || die "  Error opening file '$fname'.\n\n";
    read( FILE, $buf, 10000);
    close( FILE );

    $buf =~ m/-----${beg_str}-----\n(.*)\n-----${end_str}-----\n/s;
    return $1
	if( defined($1) );

    printf "  Failed file '$fname' contents check.\n\n";
    return "-1";
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_set_key {
    my ( $cmd, $k_file, $c_file0, $c_file1, $c_file2, $c_file3, $c_file4 ) = @_;

    if ( lc($cmd) eq "help" || $k_file eq "" || $c_file0 eq "" ) {
	printf "    audit [set]key <keys_file> <cert_file_1> [<cert_file_2> ... [<cert_file_5]]\n".
		"\tsets the key and certificate needed to sign the audit log\n".
		"\tKey Pair RSA 2048 bit. SHA1 signing. Certs: up to 5 X.509, total max length 4100 bytes.\n";
	return 0;
    }

    my @flist = ( $c_file0 );
    my $f;

    foreach $f ( $c_file1, $c_file2, $c_file3, $c_file4 ) {
	push @flist, $f if( defined($f) );
    }
    foreach $f ( $k_file, @flist ) {
	if( ! -e $f ) {
	    printf "  Error: the file '$f' isn't exist.\n\n";
	    return -1;
	}
	if( ! -f $f ) {
	    printf "  Error: the file '$f' is a special type of file.\n\n";
	    return -1;
	}
	if( ! -r $f ) {
	    printf "  Error: the file '$f' isn't readable.\n\n";
	    return -1;
	}
    }

    my ( $arg, @key_cert, $keys, @cert_len, $cert );

    $keys = sub_audit_parse_pem( $k_file, "BEGIN RSA PRIVATE KEY", "END RSA PRIVATE KEY" );
    return -1 if( $keys eq "-1" );
    $keys =~ s/\n//sg;

    foreach $f ( @flist ) {

	$arg = sub_audit_parse_pem( $f, "BEGIN CERTIFICATE", "END CERTIFICATE" );
	return -1 if( $arg eq "-1" );
	$arg =~ s/\n//sg;
	$cert .= $arg;
	push @cert_len, length( decode_base64($arg) );
    }

    printf "  Setting the key and certificate to sign the audit log: ";
    return -1 if ( !get_user_confirm() );

    push @key_cert, SOAP::Data->name( 'SignatureMechanism' => 0 );
    push @key_cert, SOAP::Data->name( 'SigningKey' => 
	    \SOAP::Data->name( 'RSAKeyPair' => 
		\SOAP::Data->name( 'DERKey' => $keys )->uri("$schemabase/CertStore/2006/01")
	    )->uri("$schemabase/CertStore/2006/01") );

    push @key_cert, SOAP::Data->name( 'LengthOfCertificates' => @cert_len );
    push @key_cert, SOAP::Data->name( 'Certificates' => $cert );

    $arg = SOAP::Data->name( 'KeyAndCert' => \SOAP::Data->value(@key_cert) );
    return do_soap $als, "SetSigningKeyingMaterial", $arg;
}

#-----------------------------------------------------------------------------------------
sub cmnd_audit_lock {
    my ( $cmd, $type, $time, $handle ) = @_;
    $type = lc( $type );

    print "\n  Warn: <handle> is required to UnLock operation!\n\n"
	if( $type eq "unlock" && !defined($handle) );

    if( (lc($cmd) eq "help") || 
	  !defined( $audit_lock_type{$type} ) ||
	( $time !~ m/^[0-9]+$/ ) || $time<1 || $time>300  ||
	( $type eq "unlock" && !defined($handle) ) ||
	( defined($handle) && $handle !~ m/^[0-9]+$/) ) {
	printf "    audit lock <type> <timeout> [<handle>]\n".
		"\tlog lock/unlock and allows audit unprovisioning\n".
		"\t<type>=lock,unprovis,unlock   <timeout>=1-300 seconds\n".
		"\t<handle> needed when log is already locked, received when locking\n";
	return 0;
    }

    print " Set Audit Lock state to ",
	    ($type eq "unlock" ? "UN" : "" ), "LOCKed",
	    ($type eq "lock" ? " within $time second".($time==1 ? "":"s") : "" ),
	    ($type eq "unprovis" ? " with ENABLED UNPROVISioning" : "" ), ": ";
    return -1 if ( ! get_user_confirm() );

    my ( @arg, $ret );

    push @arg, SOAP::Data->name( 'LockTimeoutInSeconds' => $time );
    push @arg, SOAP::Data->name( 'Flag'   => $audit_lock_type{$type} );
    push @arg, SOAP::Data->name( 'Handle' => $handle )
	if( defined($handle) );

    $ret = $als->LockAuditLog( @arg );
    print_result $ret;

    print "   Received handle: ",
	$ret->valueof('//Handle'),
	"\n   Use it for additional 'lock' requests.\n\n"
	if( defined( $ret->paramsout ) );

    return $ret->result;
}

#-------------------------------------------------------------------------------------
sub cmnd_audit {
    my @params = @_;
    my $cmd = shift;
    my $ret = -3;

    $cmd = lc($cmd);

    if ($cmd eq "help" || !@params) {
	print_usage_header();

	print "  Audit specific parameter '--num[eric]|-n': forces numeric ID codes.\n".
	       "    Service supported in AMT ver. 4.0 and later releases.\n";
	$cmd = "help";
	@params = ( $cmd );
    } else {
	print "### AMT Audit Service for '$amt_host':\n";
	return -22  if ( !check_amt_version(4,0, "  Audit not supported") );
    }

    if ( $cmd =~ m/^((info)?|help)$/ ) {
	$ret = cmnd_audit_info(@params);
    }
    if ( $cmd =~ m/^(log|help)$/ ) {
	$ret = cmnd_audit_log(@params);
    }
    if ( $cmd =~ m/^(id(list)?|help)$/ ) {
	$ret = cmnd_audit_idlist(@params);
    }
    if ( $cmd =~ m/^(enable|help)$/ ) {
	$ret = cmnd_audit_enable(@params);
    }
    if ( $cmd =~ m/^(clear(log)?|help)$/ ) {
	$ret = cmnd_audit_clear_log(@params);
    }
    if ( $cmd =~ m/^(policy|help)$/ ) {
	$ret = cmnd_audit_set_policy(@params);
    }
    if ( $cmd =~ m/^(storage|help)$/ ) {
	$ret = cmnd_audit_storage(@params);
    }
    if ( $cmd =~ m/^(sign(ature)?|help)$/ ) {
	$ret = cmnd_audit_signature(@params);
    }
    if ( $cmd =~ m/^((set)?key|help)$/ ) {
	$ret = cmnd_audit_set_key(@params);
    }
    if ( $cmd =~ m/^(lock|help)$/ ) {
	$ret = cmnd_audit_lock(@params);
    }

    print "\n" if ($cmd eq "help");

    if( $ret >= -2 ) {
	return $ret;
    } else {
	print " Unknown command: audit $cmd\n\n";
	return 10;
    }
}
#=====================================================================================



#############################################################################
# main

if (!defined($amt_host)) {
    usage();
    exit 0;
}

soap_init;

my $ret;

if( defined($amt_cmnd) ) {

    $amt_cmnd = lc($amt_cmnd);

    # params backward compatibility
    if ($amt_cmnd eq "netinfo") {
	$amt_cmnd = "net";
	@ARGV = ("info", @ARGV);
    } elsif ($amt_cmnd eq "netconf") {
	$amt_cmnd = "net";
	@ARGV = ("conf", @ARGV);
    }
    @ARGV = ( "help" ) if ( $amt_cmnd eq "help" );
} else {
    if( lc($amt_host) eq "help" ) {
	$amt_cmnd = "help";
	@ARGV = ( "help" );
    } else {
	$amt_cmnd = "info";
    }
}

sub timedout {
	print "\n## $0:$main::raised_priv Caught ALARM after ", $ENV{AMTTO} || 6, "s after call of '$main::last_alarm'\n";
	exit (1);
}
sub set_alarm {
	$main::last_alarm = $_[0];
	alarm($ENV{AMTTO} || 6);
}
$SIG{'ALRM'} = 'timedout';
&set_alarm("make connection");

# 'Harmless' commands - allow them to be run under sudo using 'root' credentials
if ($amt_cmnd eq "state") {
	$ret = &print_info_remote(1);
}
if ($main::raised_priv && $amt_cmnd eq "powerup") {
	$ret = remote_control($amt_cmnd, @ARGV);
}
if ($amt_cmnd =~ m/^(info|help)$/) {
	$ret = cmnd_info(@ARGV);
}
if ($amt_cmnd =~ m/^(hwasset|help)$/) {
	$ret = cmnd_hwasset(@ARGV);
}
if ($main::raised_priv && $amt_cmnd ne "help") {
	exit $ret if defined $ret;
	usage();
	exit -1;
}

# 'Dangerous' commands - do not allow them to be run under sudo using 'root' credentials
if ($amt_cmnd =~ m/^(net|help)$/) {
	$ret = cmnd_net(@ARGV);
}
if ($amt_cmnd =~ m/^(reset|powerup|powerdown|powercycle|setbootopt)$/) {
	$ret = remote_control($amt_cmnd, @ARGV);
}
if ($amt_cmnd =~ m/^(user|help)$/) {
	$ret = cmnd_user(@ARGV);
}
if ($amt_cmnd =~ m/^(time|help)$/) {
	$ret = cmnd_time(@ARGV);
}
if ($amt_cmnd =~ m/^(audit|help)$/) {
	$ret = cmnd_audit(@ARGV);
}
if (!defined($ret)) {
	usage();
	$ret = -1;
}

exit $ret;

# -----------------------------------------------------------------------------

# find a BMC's name
sub bmc_name {
	my ($mc) = @_;
	my ($name);
	if (($mc =~ /^[^.]*bmc($|\.)/) && gethostbyname($name = $mc)) { ; }
	elsif (gethostbyname($name = "$mc-bmc")) { ; }
	elsif (gethostbyname($name = "$mc.bmc")) { ; }
	else { $name = $mc; }
	return $name;
}
