2010-01-27 14:58:18 +00:00
#!/usr/bin/perl
# IBM(c) 2107 EPL license http://www.eclipse.org/legal/epl-v10.html
#(C)IBM Corp
#modified by jbjohnso@us.ibm.com
#This module abstracts the session management aspects of IPMI
package xCAT::IPMI ;
2010-02-12 19:24:25 +00:00
use Carp qw/confess cluck/ ;
2010-01-27 14:58:18 +00:00
BEGIN
{
$ ::XCATROOT = $ ENV { 'XCATROOT' } ? $ ENV { 'XCATROOT' } : '/opt/xcat' ;
}
use lib "$::XCATROOT/lib/perl" ;
use strict ;
use warnings "all" ;
2012-01-27 16:53:48 +00:00
use IO::Socket::INET qw/!AF_INET6 !PF_INET6/ ;
2011-12-13 19:57:29 +00:00
my $ doipv6 = eval {
require IO::Socket::INET6 ;
IO::Socket::INET6 - > import ( ) ;
require Socket6 ;
Socket6 - > import ( ) ;
1 ;
} ;
2010-01-27 14:58:18 +00:00
use IO::Select ;
2011-04-11 13:51:09 +00:00
#use Data::Dumper;
2010-01-27 14:58:18 +00:00
use Digest::MD5 qw/md5/ ;
2011-01-10 20:25:35 +00:00
my $ pendingpackets = 0 ;
my $ maxpending ; #determined dynamically based on rcvbuf detection
2010-01-28 03:42:24 +00:00
my $ ipmi2support = eval {
require Digest::SHA1 ;
Digest::SHA1 - > import ( qw/sha1/ ) ;
2010-01-28 22:06:22 +00:00
require Digest::HMAC_SHA1 ;
Digest::HMAC_SHA1 - > import ( qw/hmac_sha1/ ) ;
2010-01-28 03:42:24 +00:00
1 ;
} ;
my $ aessupport ;
if ( $ ipmi2support ) {
$ aessupport = eval {
require Crypt::Rijndael ;
require Crypt::CBC ;
1 ;
} ;
}
2010-01-27 14:58:18 +00:00
sub hexdump {
foreach ( @ _ ) {
printf "%02X " , $ _ ;
}
2010-01-28 03:42:24 +00:00
print "\n" ;
2010-01-27 14:58:18 +00:00
}
my % payload_types = ( #help readability in certain areas of code by specifying payload by name rather than number
'ipmi' = > 0 ,
'sol' = > 1 ,
'rmcpplusopenreq' = > 0x10 ,
'rmcpplusopenresponse' = > 0x11 ,
'rakp1' = > 0x12 ,
'rakp2' = > 0x13 ,
'rakp3' = > 0x14 ,
'rakp4' = > 0x15 ,
2010-01-28 03:42:24 +00:00
) ;
2010-01-27 14:58:18 +00:00
my $ socket ; #global socket for all sessions to share. Fun fun
my $ select = IO::Select - > new ( ) ;
my % bmc_handlers ; #hash from bmc address to a live session management object.
2010-01-28 03:42:24 +00:00
#only one allowed at a time per bmc
2010-01-27 14:58:18 +00:00
my % sessions_waiting ; #track session objects that may want to retry a packet, value is timestamp to 'wake' object for retransmit
sub new {
my $ proto = shift ;
my $ class = ref $ proto || $ proto ;
my $ self = { } ;
bless $ self , $ class ;
my % args = @ _ ;
2010-01-28 03:42:24 +00:00
unless ( $ ipmi2support ) {
$ self - > { ipmi15only } = 1 ;
}
2010-01-27 14:58:18 +00:00
unless ( $ args { 'bmc' } and defined $ args { 'userid' } and defined $ args { 'password' } ) {
2010-02-10 20:05:00 +00:00
$ self - > { error } = "bmc, userid, and password must be specified" ;
return $ self ;
2010-01-27 14:58:18 +00:00
}
foreach ( keys % args ) { #store all passed parameters
$ self - > { $ _ } = $ args { $ _ } ;
}
unless ( $ args { 'port' } ) { #default to port 623 unless specified
$ self - > { 'port' } = 623 ;
}
unless ( $ socket ) {
2011-12-13 19:57:29 +00:00
if ( $ doipv6 ) {
$ socket = IO::Socket::INET6 - > new ( Proto = > 'udp' ) ;
} else {
$ socket = IO::Socket::INET - > new ( Proto = > 'udp' ) ;
}
2011-01-10 20:25:35 +00:00
if ( - r "/proc/sys/net/core/rmem_max" ) { # we can detect the maximum allowed socket, read it.
my $ sysctl ;
open ( $ sysctl , "<" , "/proc/sys/net/core/rmem_max" ) ;
my $ maxrcvbuf = <$sysctl> ;
my $ rcvbuf = $ socket - > sockopt ( SO_RCVBUF ) ;
if ( $ maxrcvbuf > $ rcvbuf ) {
$ socket - > sockopt ( SO_RCVBUF , $ maxrcvbuf / 2 ) ;
}
$ maxpending = $ maxrcvbuf / 1500 ; #probably could have maxpending go higher, but just go with typical MTU as a guess
} else { #We do not have a way to determine how high we could set RCVBUF, so read the current value and run with it
my $ rcvbuf = $ socket - > sockopt ( SO_RCVBUF ) ;
$ maxpending = $ rcvbuf / 1500 ; #probably could have maxpending go higher, but just go with typical MTU as a guess
}
2010-01-27 14:58:18 +00:00
$ select - > add ( $ socket ) ;
}
2010-02-10 20:05:00 +00:00
my $ bmc_n ;
2011-12-13 19:57:29 +00:00
my ( $ family , $ socktype , $ protocol , $ saddr , $ name , $ ip , $ service ) ;
if ( $ doipv6 ) {
( $ family , $ socktype , $ protocol , $ saddr , $ name ) = Socket6:: getaddrinfo ( $ self - > { bmc } , 623 , AF_UNSPEC , SOCK_DGRAM , 0 ) ;
2012-02-07 13:34:20 +00:00
( $ ip , $ service ) = getnameinfo ( $ saddr , Socket6:: NI_NUMERICHOST ( ) ) ;
2011-12-13 19:57:29 +00:00
}
unless ( $ saddr or $ bmc_n = inet_aton ( $ self - > { bmc } ) ) {
2010-02-10 20:05:00 +00:00
$ self - > { error } = "Could not resolve " . $ self - > { bmc } . " to an address" ;
return $ self ;
}
2011-12-13 19:59:42 +00:00
if ( $ ip and $ ip =~ /::ffff:\d+\.\d+\.+\d+\.\d+/ ) {
2011-12-13 19:57:29 +00:00
$ ip =~ s/::ffff:// ;
2011-12-13 19:59:42 +00:00
} elsif ( not $ ip and $ bmc_n ) {
2011-12-13 19:57:29 +00:00
$ ip = inet_ntoa ( $ bmc_n ) ;
}
$ bmc_handlers { $ ip } = $ self ;
if ( $ saddr ) {
$ self - > { peeraddr } = $ saddr ;
} else {
$ self - > { peeraddr } = sockaddr_in ( $ self - > { port } , $ bmc_n ) ;
}
2010-01-27 14:58:18 +00:00
$ self - > { 'sequencenumber' } = 0 ; #init sequence number
2010-01-28 03:42:24 +00:00
$ self - > { 'sequencenumberbytes' } = [ 0 , 0 , 0 , 0 ] ; #init sequence number
$ self - > { 'sessionid' } = [ 0 , 0 , 0 , 0 ] ; # init session id
$ self - > { 'authtype' } = 0 ; # first messages will have auth type of 0
$ self - > { 'ipmiversion' } = '1.5' ; # send first packet as 1.5
2010-02-12 19:24:25 +00:00
$ self - > { 'timeout' } = 2 ; #start at a quick timeout, increase on retry
2010-01-28 03:42:24 +00:00
$ self - > { 'seqlun' } = 0 ; #the IPMB seqlun combo, increment by 4s
$ self - > { 'logged' } = 0 ;
2010-01-27 14:58:18 +00:00
return $ self ;
}
sub login {
my $ self = shift ;
my % args = @ _ ;
if ( $ self - > { logged } ) {
$ args { callback } - > ( "SUCCESS" , $ args { callback_args } ) ;
return ;
}
$ self - > { onlogon } = $ args { callback } ;
$ self - > { onlogon_args } = $ args { callback_args } ;
$ self - > get_channel_auth_cap ( ) ;
}
sub logout {
my $ self = shift ;
my % args = @ _ ;
$ self - > { onlogout } = $ args { callback } ;
$ self - > { onlogout_args } = $ args { callback_args } ;
2010-02-05 18:11:12 +00:00
unless ( $ self - > { logged } ) {
if ( $ self - > { onlogout } ) { $ self - > { onlogout } - > ( "SUCCESS" , $ self - > { onlogout_args } ) ; }
return ;
}
2010-01-27 14:58:18 +00:00
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3c , data = > $ self - > { sessionid } , callback = > \ & logged_out , callback_args = > $ self ) ;
}
sub logged_out {
my $ rsp = shift ;
my $ self = shift ;
2011-01-10 20:25:35 +00:00
if ( defined $ rsp - > { code } and $ rsp - > { code } == 0 ) {
2010-01-27 14:58:18 +00:00
$ self - > { logged } = 0 ;
if ( $ self - > { onlogout } ) {
2010-01-28 03:42:24 +00:00
$ self - > { onlogout } - > ( "SUCCESS" , $ self - > { onlogout_args } ) ;
2010-01-27 14:58:18 +00:00
}
} else {
2010-01-28 03:42:24 +00:00
if ( $ self - > { onlogout } ) {
$ self - > { onlogout } - > ( "ERROR:" , $ self - > { onlogout_args } ) ;
}
2010-01-27 14:58:18 +00:00
}
}
sub get_channel_auth_cap { #implement special case for session management command
my $ self = shift ;
if ( defined $ self - > { ipmi15only } ) {
$ self - > subcmd ( netfn = > 0x6 , command = > 0x38 , data = > [ 0x0e , 0x04 ] , callback = > \ & got_channel_auth_cap , callback_args = > $ self ) ;
} else {
$ self - > subcmd ( netfn = > 0x6 , command = > 0x38 , data = > [ 0x8e , 0x04 ] , callback = > \ & got_channel_auth_cap , callback_args = > $ self ) ;
}
2010-01-28 03:42:24 +00:00
#0x8e, set bit to signify recognition of IPMI 2.0 and request channel 'e', current.
#0x04, request administrator privilege
2010-01-27 14:58:18 +00:00
}
sub get_session_challenge {
my $ self = shift ;
my @ user ;
if ( $ self - > { userbytes } ) {
@ user = @ { $ self - > { userbytes } } ;
} else {
@ user = unpack ( "C*" , $ self - > { userid } ) ;
for ( my $ i = scalar @ user ; $ i < 16 ; $ i + + ) {
$ user [ $ i ] = 0 ;
}
$ self - > { userbytes } = \ @ user ;
}
$ self - > subcmd ( netfn = > 0x6 , command = > 0x39 , data = > [ 2 , @ user ] , callback = > \ & got_session_challenge , callback_args = > $ self ) ; #we only support MD5, we would have errored out if not supported
}
sub got_session_challenge {
my $ rsp = shift ;
my $ self = shift ;
my @ data = @ { $ rsp - > { data } } ;
my % localcodes = ( 0x81 = > "Invalid user name" , 0x82 = > "null user disabled" ) ;
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Get challenge failed with %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " ($localcodes{$code})" ;
} #TODO: generic codes
2010-01-27 14:58:18 +00:00
2010-01-28 03:42:24 +00:00
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
return ;
}
2010-01-27 14:58:18 +00:00
$ self - > { sessionid } = [ splice @ data , 0 , 4 ] ;
$ self - > { authtype } = 2 ; #switch to auth mode
2010-01-28 03:42:24 +00:00
$ self - > activate_session ( @ data ) ;
2010-01-27 14:58:18 +00:00
}
sub activate_session {
my $ self = shift ;
my @ challenge = @ _ ;
my @ data = ( 2 , 4 , @ challenge , 1 , 0 , 0 , 0 ) ;
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3a , data = > \ @ data , callback = > \ & session_activated , callback_args = > $ self ) ;
}
sub session_activated {
my $ rsp = shift ;
my $ self = shift ;
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
my % localcodes = (
0x81 = > "No available login slots" ,
0x82 = > "No available login slots for " . $ self - > { userid } ,
0x83 = > "No slot available as administrator" ,
0x84 = > "Session sequence number out of range" ,
0x85 = > "Invalid session ID" ,
0x86 = > $ self - > { userid } . " is not allowed to be Administrator or Administrator not allowed over network" ,
) ;
2010-01-27 14:58:18 +00:00
my @ data = @ { $ rsp - > { data } } ;
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Unable to log in to BMC due to code %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " ($localcodes{$code})" ;
}
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
}
$ self - > { sessionid } = [ splice @ data , 1 , 4 ] ;
$ self - > { sequencenumber } = $ data [ 1 ] + ( $ data [ 2 ] << 8 ) + ( $ data [ 3 ] << 16 ) + ( $ data [ 4 ] << 24 ) ;
$ self - > { sequencenumberbytes } = [ splice @ data , 1 , 4 ] ;
$ self - > set_admin_level ( ) ;
}
sub set_admin_level {
my $ self = shift ;
$ self - > subcmd ( netfn = > 0x6 , command = > 0x3b , data = > [ 4 ] , callback = > \ & admin_level_set , callback_args = > $ self ) ;
}
sub admin_level_set {
my $ rsp = shift ;
my $ self = shift ;
my % localcodes = (
2010-01-28 03:42:24 +00:00
0x80 = > $ self - > { userid } . " is not allowed administrator access" ,
0x81 = > "This user or channel is not allowed administrator access" ,
0x82 = > "Cannot disable User Level authentication" ,
) ;
2010-01-27 14:58:18 +00:00
my $ code = $ rsp - > { code } ;
if ( $ code ) {
my $ errtxt = sprintf ( "ERROR: Failed requesting administrator privilege %02xh" , $ code ) ;
if ( $ localcodes { $ code } ) {
$ errtxt . = " (" . $ localcodes { $ code } . ")" ;
}
$ self - > { onlogon } - > ( $ errtxt , $ self - > { onlogon_args } ) ;
} else {
$ self - > { logged } = 1 ;
$ self - > { onlogon } - > ( "SUCCESS" , $ self - > { onlogon_args } ) ;
}
}
sub got_channel_auth_cap {
my $ rsp = shift ;
my $ self = shift ;
2010-02-05 18:17:27 +00:00
if ( $ rsp - > { error } ) {
$ self - > { onlogon } - > ( "ERROR: " . $ rsp - > { error } , $ self - > { onlogon_args } ) ;
return ;
}
2010-01-27 14:58:18 +00:00
my $ code = $ rsp - > { code } ; #just to save me some typing
2010-01-28 03:42:24 +00:00
if ( $ code == 0xcc and not defined $ self - > { ipmi15only } ) { #ok, most likely a stupid ipmi 1.5 bmc
$ self - > { ipmi15only } = 1 ;
return $ self - > get_channel_auth_cap ( ) ;
}
2010-01-27 14:58:18 +00:00
if ( $ code != 0 ) {
$ self - > { onlogon } - > ( "ERROR: Get channel capabilities failed with $code" , $ self - > { onlogon_args } ) ;
return ;
}
my @ data = @ { $ rsp - > { data } } ;
$ self - > { currentchannel } = $ data [ 0 ] ;
2010-01-28 03:42:24 +00:00
if ( ( $ data [ 1 ] & 0b10000000 ) and ( $ data [ 3 ] & 0b10 ) ) {
$ self - > { ipmiversion } = '2.0' ;
}
2010-01-27 14:58:18 +00:00
if ( $ self - > { ipmiversion } eq '1.5' ) {
2010-01-28 03:42:24 +00:00
unless ( $ data [ 1 ] & 0b100 ) {
$ self - > { onlogon } - > ( "ERROR: MD5 is required but not enabeld or available on target BMC" , $ self - > { onlogon_args } ) ;
}
2010-01-27 14:58:18 +00:00
$ self - > get_session_challenge ( ) ;
} elsif ( $ self - > { ipmiversion } eq '2.0' ) { #do rmcp+
2010-01-28 03:42:24 +00:00
$ self - > open_rmcpplus_request ( ) ;
2010-01-27 14:58:18 +00:00
}
}
2010-01-28 03:42:24 +00:00
sub open_rmcpplus_request {
my $ self = shift ;
$ self - > { 'authtype' } = 6 ;
2010-01-28 22:06:22 +00:00
$ self - > { sidm } = [ 0x15 , 0x58 , 0x25 , 0x7a ] ;
2010-01-28 03:42:24 +00:00
my @ payload = ( 0x1f , #message tag, TODO: could be random
0 , #requested privilege role, 0 is highest allowed
0 , 0 , #reserved
2010-01-28 22:06:22 +00:00
0x15 , 0x58 , 0x25 , 0x7a , #we only have to sweat one session, so no need to generate
2010-01-28 03:42:24 +00:00
0 , 0 , 0 , 8 , 1 , 0 , 0 , 0 , #table 13-17, request sha
1 , 0 , 0 , 8 , 1 , 0 , 0 , 0 ) ; #sha integrity
if ( $ aessupport ) {
push @ payload , ( 2 , 0 , 0 , 8 , 1 , 0 , 0 , 0 ) ;
} else {
push @ payload , ( 2 , 0 , 0 , 8 , 0 , 0 , 0 , 0 ) ;
}
2010-02-12 19:24:25 +00:00
$ self - > { sessionestablishmentcontext } = 'opensession' ;
2010-01-28 03:42:24 +00:00
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rmcpplusopenreq' } ) ;
}
2010-01-27 14:58:18 +00:00
sub checksum {
my $ self = shift ;
my $ sum = 0 ;
foreach ( @ _ ) {
$ sum += $ _ ;
}
$ sum = ~ $ sum + 1 ;
return ( $ sum & 0xff ) ;
}
sub subcmd {
my $ self = shift ;
my % args = @ _ ;
my $ rqaddr = 0x81 ; #see section 5.5 of ipmi2 spec, rqsa by old code
my $ rsaddr = 0x20 ; #figrue 13-4, rssa by old code
my @ rnl = ( $ rsaddr , $ args { netfn } << 2 ) ;
my @ rest = ( $ rqaddr , $ self - > { seqlun } , $ args { command } , @ { $ args { data } } ) ;
my @ payload = ( @ rnl , $ self - > checksum ( @ rnl ) , @ rest , $ self - > checksum ( @ rest ) ) ;
$ self - > { seqlun } += 4 ; #increment by 1<<2
$ self - > { seqlun } & = 0xff ; #keep it one byte
$ self - > { ipmicallback } = $ args { callback } ;
$ self - > { ipmicallback_args } = $ args { callback_args } ;
2010-01-28 22:06:22 +00:00
my $ type = $ payload_types { 'ipmi' } ;
if ( $ self - > { integrityalgo } ) {
$ type = $ type | 0b01000000 ; #add integrity
}
2010-01-29 04:15:52 +00:00
if ( $ self - > { confalgo } ) {
$ type = $ type | 0b10000000 ; #add secrecy
}
2012-02-27 17:49:29 +00:00
$ self - > sendpayload ( payload = > \ @ payload , type = > $ type , delayxmit = > $ args { delayxmit } ) ;
2010-01-27 14:58:18 +00:00
}
sub waitforrsp {
my $ self = shift ;
my $ data ;
my $ peerport ;
my $ peerhost ;
my $ timeout ; #TODO: code to scan pending objects to find soonest retry deadline
my $ curtime = time ( ) ;
2010-02-05 18:11:12 +00:00
foreach ( keys % sessions_waiting ) {
if ( $ sessions_waiting { $ _ } - > { timeout } <= $ curtime ) { #retry or fail..
my $ session = $ sessions_waiting { $ _ } - > { ipmisession } ;
delete $ sessions_waiting { $ _ } ;
2011-01-10 20:25:35 +00:00
$ pendingpackets -= 1 ;
2010-02-05 18:11:12 +00:00
$ session - > timedout ( ) ;
next ;
}
2010-01-27 14:58:18 +00:00
if ( defined $ timeout ) {
2010-02-05 18:11:12 +00:00
if ( $ timeout < $ sessions_waiting { $ _ } - > { timeout } - $ curtime ) {
2010-01-27 14:58:18 +00:00
next ;
}
}
2010-02-05 18:11:12 +00:00
$ timeout = $ sessions_waiting { $ _ } - > { timeout } - $ curtime ;
}
unless ( defined $ timeout ) {
return scalar ( keys % sessions_waiting ) ;
2010-01-27 14:58:18 +00:00
}
if ( $ select - > can_read ( $ timeout ) ) {
while ( $ select - > can_read ( 0 ) ) {
$ peerport = $ socket - > recv ( $ data , 1500 , 0 ) ;
route_ipmiresponse ( $ peerport , unpack ( "C*" , $ data ) ) ;
}
}
return scalar ( keys % sessions_waiting ) ;
}
2010-02-05 18:11:12 +00:00
sub timedout {
my $ self = shift ;
$ self - > { timeout } = $ self - > { timeout } + 1 ;
if ( $ self - > { timeout } > 4 ) { #giveup, really
2010-02-12 19:24:25 +00:00
$ self - > { timeout } = 2 ;
2010-02-05 18:17:27 +00:00
my $ rsp = { } ;
$ rsp - > { error } = "timeout" ;
$ self - > { ipmicallback } - > ( $ rsp , $ self - > { ipmicallback_args } ) ;
2010-02-05 18:11:12 +00:00
return ;
}
$ self - > sendpayload ( % { $ self - > { pendingargs } } ) ;
}
2010-01-27 14:58:18 +00:00
sub route_ipmiresponse {
my $ sockaddr = shift ;
my @ rsp = @ _ ;
unless (
$ rsp [ 0 ] == 0x6 and
$ rsp [ 2 ] == 0xff and
$ rsp [ 3 ] == 0x07 ) {
return ; #ignore non-ipmi packets
}
my $ host ;
my $ port ;
2011-12-13 19:57:29 +00:00
#($port,$host) = sockaddr_in6($sockaddr);
#$host = inet_ntoa($host);
if ( $ doipv6 ) {
2012-02-07 13:34:20 +00:00
( $ host , $ port ) = getnameinfo ( $ sockaddr , Socket6:: NI_NUMERICHOST ( ) ) ;
2011-12-13 19:57:29 +00:00
} else {
( $ port , $ host ) = sockaddr_in ( $ sockaddr ) ;
$ host = inet_ntoa ( $ host ) ;
}
if ( $ host =~ /::ffff:\d+\.\d+\.+\d+\.\d+/ ) {
$ host =~ s/::ffff:// ;
}
2010-01-27 14:58:18 +00:00
if ( $ bmc_handlers { $ host } ) {
2011-01-10 20:25:35 +00:00
$ pendingpackets -= 1 ;
2010-01-27 14:58:18 +00:00
$ bmc_handlers { $ host } - > handle_ipmi_packet ( @ rsp ) ;
}
}
sub handle_ipmi_packet {
2010-02-12 19:24:25 +00:00
#return zero if we like the response
2010-01-27 14:58:18 +00:00
my $ self = shift ;
my @ rsp = @ _ ;
if ( $ rsp [ 4 ] == 0 or $ rsp [ 4 ] == 2 ) { #IPMI 1.5 (check 0 assumption...)
my $ remsequencenumber = $ rsp [ 5 ] + $ rsp [ 6 ] >> 8 + $ rsp [ 7 ] >> 16 + $ rsp [ 8 ] >> 24 ;
if ( $ self - > { remotesequencenumber } and $ remsequencenumber < $ self - > { remotesequencenumber } ) {
2010-02-12 19:24:25 +00:00
return 5 ; #ignore malformed sequence number
2010-01-27 14:58:18 +00:00
}
$ self - > { remotesequencenumber } = $ remsequencenumber ;
$ self - > { remotesequencebytes } = [ @ rsp [ 5 .. 8 ] ] ;
if ( $ rsp [ 4 ] != $ self - > { authtype } ) {
return 2 ; # not thinking about packets that do not match our preferred auth type
}
unless ( $ rsp [ 9 ] == $ self - > { sessionid } - > [ 0 ] and
$ rsp [ 10 ] == $ self - > { sessionid } - > [ 1 ] and
$ rsp [ 11 ] == $ self - > { sessionid } - > [ 2 ] and
$ rsp [ 12 ] == $ self - > { sessionid } - > [ 3 ] ) {
return 1 ; #this response does not match our current session id, ignore it
}
my @ authcode = ( ) ;
if ( $ rsp [ 4 ] == 2 ) {
@ authcode = splice @ rsp , 13 , 16 ;
}
my @ payload = splice ( @ rsp , 14 , $ rsp [ 13 ] ) ;
if ( @ authcode ) { #authcode is longer than 0, check it
$ self - > { checkremotecode } = 1 ;
my @ expectedauthcode = $ self - > ipmi15authcode ( @ payload ) ;
$ self - > { checkremotecode } = 0 ;
foreach ( 0 .. 15 ) {
if ( $ expectedauthcode [ $ _ ] != $ authcode [ $ _ ] ) {
return 3 ; #invalid authcode
}
}
}
2010-02-12 19:24:25 +00:00
return $ self - > parse_ipmi_payload ( @ payload ) ;
2010-01-27 14:58:18 +00:00
} elsif ( $ rsp [ 4 ] == 6 ) { #IPMI 2.0
2010-01-28 03:42:24 +00:00
if ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x11 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rmcp_response ( splice @ rsp , 16 ) ; #the function always leaves ourselves waiting, no need to deregister
2010-01-28 22:06:22 +00:00
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x13 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rakp2 ( splice @ rsp , 16 ) ; #same as above
2010-01-28 22:06:22 +00:00
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x15 ) {
2010-02-12 19:24:25 +00:00
return $ self - > got_rakp4 ( splice @ rsp , 16 ) ; #same as above
} elsif ( ( $ rsp [ 5 ] & 0b00111111 ) == 0x0 ) { #ipmi payload, sophisticated logic to follow
2010-01-29 03:17:19 +00:00
my $ encrypted ;
if ( $ rsp [ 5 ] & 0b10000000 ) {
$ encrypted = 1 ;
}
unless ( $ rsp [ 5 ] & 0b01000000 ) {
2010-02-12 19:24:25 +00:00
return 3 ; #we refuse to examine unauthenticated packets in this context
2010-01-29 03:17:19 +00:00
}
2010-02-12 19:24:25 +00:00
splice ( @ rsp , 0 , 4 ) ; #ditch the rmcp header
my @ authcode = splice ( @ rsp , - 12 ) ; #strip away authcode and remember it
2010-01-29 03:17:19 +00:00
my @ expectedcode = unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ rsp ) , $ self - > { k1 } ) ) ;
splice ( @ expectedcode , 12 ) ;
foreach ( @ expectedcode ) {
unless ( $ _ == shift @ authcode ) {
2010-02-12 19:24:25 +00:00
return 3 ; #authcode bad, pretend it never existed
2010-01-29 03:17:19 +00:00
}
}
2010-02-12 19:24:25 +00:00
unless ( $ rsp [ 2 ] == 0x15 and
$ rsp [ 3 ] == 0x58 and
$ rsp [ 4 ] == 0x25 and
$ rsp [ 5 ] == 0x7a ) {
return 1 ; #this response does not match our current session id, ignore it
}
my $ remsequencenumber = $ rsp [ 6 ] + $ rsp [ 7 ] >> 8 + $ rsp [ 8 ] >> 16 + $ rsp [ 9 ] >> 24 ;
if ( $ self - > { remotesequencenumber } and $ remsequencenumber < $ self - > { remotesequencenumber } ) {
return 5 ; #ignore malformed sequence number
}
$ self - > { remotesequencenumber } = $ remsequencenumber ;
2010-01-29 04:40:10 +00:00
my $ psize = $ rsp [ 10 ] + ( $ rsp [ 11 ] << 8 ) ;
my @ payload = splice ( @ rsp , 12 , $ psize ) ;
2010-01-29 03:17:19 +00:00
if ( $ encrypted ) {
2010-01-29 04:40:10 +00:00
my $ iv = pack ( "C*" , splice @ payload , 0 , 16 ) ;
2010-01-30 01:06:16 +00:00
my $ cipher = Crypt::CBC - > new ( - literal_key = > 1 , - key = > $ self - > { aeskey } , - cipher = > "Crypt::Rijndael" , - header = > "none" , - iv = > $ iv , - keysize = > 16 , - blocksize = > 16 , - padding = > \ & cbc_pad ) ;
my $ crypted = pack ( "C*" , @ payload ) ;
@ payload = unpack ( "C*" , $ cipher - > decrypt ( $ crypted ) ) ;
2010-01-29 03:17:19 +00:00
}
2010-02-12 19:24:25 +00:00
return $ self - > parse_ipmi_payload ( @ payload ) ;
} else {
return 6 ; #unsupported payload
2010-01-28 03:42:24 +00:00
}
2010-02-12 19:24:25 +00:00
} else {
return 7 ; #unsupported ASF traffic
2010-01-27 14:58:18 +00:00
}
}
2010-01-30 01:06:16 +00:00
sub cbc_pad {
my $ block = shift ;
my $ size = shift ;
my $ mode = shift ;
if ( $ mode eq 'e' ) {
my $ neededpad = $ size - length ( $ block ) %$ size ;
$ neededpad -= 1 ;
my @ pad = unpack ( "C*" , $ block ) ;
foreach ( 1 .. $ neededpad ) {
push @ pad , $ _ ;
}
push @ pad , $ neededpad ;
return pack ( "C*" , @ pad ) ;
} elsif ( $ mode eq 'd' ) {
my @ block = unpack ( "C*" , $ block ) ;
my $ count = pop @ block ;
2010-01-31 18:39:53 +00:00
unless ( $ count ) {
return pack ( "C*" , @ block ) ;
}
2010-01-30 01:06:16 +00:00
splice @ block , 0 - $ count ;
return pack ( "C*" , @ block ) ;
}
}
2010-01-28 22:06:22 +00:00
2010-01-28 03:42:24 +00:00
sub got_rmcp_response {
my $ self = shift ;
2010-01-28 22:06:22 +00:00
my @ data = @ _ ;
my $ byte = shift @ data ;
2010-02-12 19:24:25 +00:00
unless ( $ self - > { sessionestablishmentcontext } eq 'opensession' ) {
return 9 ; #now's not the time for this response, ignore it
}
2010-01-28 22:06:22 +00:00
unless ( $ byte == 0x1f ) {
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte >= 4 ) {
$ self - > { onlogon } - > ( "ERROR: Cannot acquire sufficient privilege" , $ self - > { onlogon_args } ) ;
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 5 ;
$ self - > { pendingsessionid } = [ splice @ data , 0 , 4 ] ;
2010-02-12 19:24:25 +00:00
$ self - > { sessionestablishmentcontext } = 'rakp2' ;
2010-01-28 22:06:22 +00:00
$ self - > send_rakp1 ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 22:06:22 +00:00
}
sub send_rakp3 {
my $ self = shift ;
my @ payload = ( 0x1f , 0 , 0 , 0 , @ { $ self - > { pendingsessionid } } ) ;
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
push @ payload , unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ { $ self - > { remoterandomnumber } } , @ { $ self - > { sidm } } , 4 , scalar @ user , @ user ) , $ self - > { password } ) ) ;
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rakp3' } ) ;
}
sub send_rakp1 {
my $ self = shift ;
my @ payload = ( 0x1f , 0 , 0 , 0 , @ { $ self - > { pendingsessionid } } ) ;
$ self - > { randomnumber } = [] ;
foreach ( 1 .. 16 ) {
my $ randomnumber = int ( rand ( 255 ) ) ;
push @ { $ self - > { randomnumber } } , $ randomnumber ;
}
push @ payload , @ { $ self - > { randomnumber } } ;
push @ payload , ( 4 , 0 , 0 ) ; # request admin
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
push @ payload , scalar @ user ;
push @ payload , @ user ;
$ self - > sendpayload ( payload = > \ @ payload , type = > $ payload_types { 'rakp1' } ) ;
}
sub got_rakp4 {
my $ self = shift ;
my @ data = @ _ ;
my $ byte = shift @ data ;
2010-02-12 19:24:25 +00:00
unless ( $ self - > { sessionestablishmentcontext } eq 'rakp4' ) {
return 9 ; #now's not the time for this response, ignore it
}
2010-01-28 22:06:22 +00:00
unless ( $ byte == 0x1f ) {
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 6 ; #discard reserved bytes and session id
my @ expectauthcode = unpack ( "C*" , hmac_sha1 ( pack ( "C*" , @ { $ self - > { randomnumber } } , @ { $ self - > { pendingsessionid } } , @ { $ self - > { remoteguid } } ) , $ self - > { sik } ) ) ;
foreach ( @ expectauthcode [ 0 .. 11 ] ) {
unless ( $ _ == ( shift @ data ) ) {
$ self - > { onlogon } - > ( "ERROR: failure in final rakp exchange message" , $ self - > { onlogon_args } ) ;
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
}
$ self - > { sessionid } = $ self - > { pendingsessionid } ;
$ self - > { integrityalgo } = 'sha1' ;
2010-01-29 04:15:52 +00:00
if ( $ aessupport ) {
$ self - > { confalgo } = 'aes' ;
}
2010-01-29 02:32:47 +00:00
$ self - > { sequencenumber } = 1 ;
$ self - > { sequencenumberbytes } = [ 1 , 0 , 0 , 0 ] ;
2010-02-12 19:24:25 +00:00
$ self - > { sessionestablishmentcontext } = 'done' ; #will move on to relying upon session sequence number
2010-01-28 22:06:22 +00:00
$ self - > set_admin_level ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 22:06:22 +00:00
}
sub got_rakp2 {
my $ self = shift ;
my @ data = @ _ ;
my $ byte = shift @ data ;
2010-02-12 19:24:25 +00:00
unless ( $ self - > { sessionestablishmentcontext } eq 'rakp2' ) {
return 9 ; #now's not the time for this response, ignore it
}
2010-01-28 22:06:22 +00:00
unless ( $ byte == 0x1f ) {
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
$ byte = shift @ data ;
unless ( $ byte == 0x00 ) {
$ self - > { onlogon } - > ( "ERROR: $byte code on opening RMCP+ session" , $ self - > { onlogon_args } ) ; #TODO: errors
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
splice @ data , 0 , 6 ; # throw away reserved bytes, and session id, might need to check
$ self - > { remoterandomnumber } = [] ;
foreach ( 1 .. 16 ) {
push @ { $ self - > { remoterandomnumber } } , ( shift @ data ) ;
}
$ self - > { remoteguid } = [] ;
foreach ( 1 .. 16 ) {
push @ { $ self - > { remoteguid } } , ( shift @ data ) ;
}
#Data now represents authcode.. sha1 only..
my @ user = unpack ( "C*" , $ self - > { userid } ) ;
my $ ulength = scalar @ user ;
my $ hmacdata = pack ( "C*" , ( 0x15 , 0x58 , 0x25 , 0x7a , @ { $ self - > { pendingsessionid } } , @ { $ self - > { randomnumber } } , @ { $ self - > { remoterandomnumber } } , @ { $ self - > { remoteguid } } , 4 , $ ulength , @ user ) ) ;
my @ expectedhash = ( unpack ( "C*" , hmac_sha1 ( $ hmacdata , $ self - > { password } ) ) ) ;
foreach ( 0 .. ( scalar ( @ expectedhash ) - 1 ) ) {
if ( $ expectedhash [ $ _ ] != $ data [ $ _ ] ) {
$ self - > { onlogon } - > ( "ERROR: Incorrect password provided" , $ self - > { onlogon_args } ) ;
2010-02-12 19:24:25 +00:00
return 9 ;
2010-01-28 22:06:22 +00:00
}
}
$ self - > { sik } = hmac_sha1 ( pack ( "C*" , @ { $ self - > { randomnumber } } , @ { $ self - > { remoterandomnumber } } , 4 , $ ulength , @ user ) , $ self - > { password } ) ;
2010-01-29 02:32:47 +00:00
$ self - > { k1 } = hmac_sha1 ( pack ( "C*" , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ) , $ self - > { sik } ) ;
2010-01-29 04:15:52 +00:00
if ( $ aessupport ) {
$ self - > { k2 } = hmac_sha1 ( pack ( "C*" , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ) , $ self - > { sik } ) ;
my @ aeskey = unpack ( "C*" , $ self - > { k2 } ) ;
$ self - > { aeskey } = pack ( "C*" , ( splice @ aeskey , 0 , 16 ) ) ;
}
2010-02-12 19:24:25 +00:00
$ self - > { sessionestablishmentcontext } = 'rakp4' ;
2010-01-28 22:06:22 +00:00
$ self - > send_rakp3 ( ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-28 03:42:24 +00:00
}
2010-01-27 14:58:18 +00:00
sub parse_ipmi_payload {
my $ self = shift ;
my @ payload = @ _ ;
#for now, just trash the headers, this has been validated to death anyway
2010-02-12 19:24:25 +00:00
#except seqlun, that one is important
if ( $ payload [ 4 ] != ( $ self - > { seqlun } ? $ self - > { seqlun } - 4 : 252 ) ) {
print "Successfully didn't get confused by stale response " . $ payload [ 4 ] . " and " . ( $ self - > { seqlun } - 4 ) . "\n" ;
hexdump ( @ payload ) ;
return 1 ; #response mismatch
}
delete $ sessions_waiting { $ self } ; #deregister self as satisfied, callback will reregister if appropriate
2010-01-27 14:58:18 +00:00
splice @ payload , 0 , 5 ; #remove rsaddr/netfs/lun/checksum/rq/seq/lun
pop @ payload ; #remove checksum
my $ rsp ;
$ rsp - > { cmd } = shift @ payload ;
$ rsp - > { code } = shift @ payload ;
$ rsp - > { data } = \ @ payload ;
$ self - > { ipmicallback } - > ( $ rsp , $ self - > { ipmicallback_args } ) ;
2010-02-12 19:24:25 +00:00
return 0 ;
2010-01-27 14:58:18 +00:00
}
sub ipmi15authcode {
my $ self = shift ;
#per table 22-22 'authcode algorithms'
my @ data = @ _ ;
my @ password ;
my @ code ;
if ( $ self - > { passbytes } ) {
@ password = @ { $ self - > { passbytes } } ;
} else {
@ password = unpack ( "C*" , $ self - > { password } ) ;
for ( my $ i = scalar @ password ; $ i < 16 ; $ i + + ) {
$ password [ $ i ] = 0 ;
}
$ self - > { passbytes } = \ @ password ;
}
my @ sequencebytes = @ { $ self - > { sequencenumberbytes } } ;
if ( $ self - > { checkremotecode } ) {
@ sequencebytes = @ { $ self - > { remotesequencebytes } } ;
}
if ( $ self - > { authtype } == 0 ) {
return ( ) ;
} elsif ( $ self - > { authtype } == 2 ) {
return unpack ( "C*" , md5 ( pack ( "C*" , @ password , @ { $ self - > { sessionid } } , @ data , @ sequencebytes , @ password ) ) ) ; #ignoring single-session channels
}
#Not supporting plaintext passwords, that would be asinine
}
#this function accepts a generic ipmi command and applies current session data and handles the 1.5<->2.0 differences
sub sendpayload {
#implementation used section 13.6, examle ipmi over lan packet
my $ self = shift ;
my % args = @ _ ;
my @ msg = ( 0x6 , 0x0 , 0xff , 0x07 ) ; #RMCP header is constant in IPMI
2010-01-28 22:06:22 +00:00
my $ type = $ args { type } & 0b00111111 ;
2010-02-05 18:11:12 +00:00
$ sessions_waiting { $ self } = { } ;
$ sessions_waiting { $ self } - > { ipmisession } = $ self ;
2010-01-27 14:58:18 +00:00
my @ payload = @ { $ args { payload } } ;
2010-02-05 18:11:12 +00:00
$ self - > { pendingargs } = \ % args ;
2012-02-27 17:49:29 +00:00
if ( $ args { delayxmit } ) {
$ sessions_waiting { $ self } - > { timeout } = time ( ) + $ args { delayxmit } ;
$ self - > { timeout } = 1 ; #since we are burning one of the retry attempts, start the backoff algorithm faster to make it come out even
undef $ args { delayxmit } ;
return ; #don't actually transmit packet, use retry timer to start us off
} else {
$ sessions_waiting { $ self } - > { timeout } = time ( ) + $ self - > { timeout } ;
}
2010-01-27 14:58:18 +00:00
push @ msg , $ self - > { 'authtype' } ; # add authtype byte (will support 0 only for session establishment, 2 for ipmi 1.5, 6 for ipmi2
if ( $ self - > { 'ipmiversion' } eq '2.0' ) { #TODO: revisit this to see if assembly makes sense
push @ msg , $ args { type } ;
2010-01-28 22:06:22 +00:00
if ( $ type == 2 ) {
2010-01-27 14:58:18 +00:00
push @ msg , @ { $ self - > { 'iana' } } , 0 ;
push @ msg , @ { $ self - > { 'oem_payload_id' } } ;
}
push @ msg , @ { $ self - > { sessionid } } ;
}
push @ msg , @ { $ self - > { sequencenumberbytes } } ;
if ( $ self - > { 'ipmiversion' } eq '1.5' ) { #ipmi 2.0 for some reason swapped session id and seq number location
push @ msg , @ { $ self - > { sessionid } } ;
unless ( $ self - > { authtype } == 0 ) {
push @ msg , $ self - > ipmi15authcode ( @ payload ) ;
}
push @ msg , scalar ( @ payload ) ;
push @ msg , @ payload ;
#TODO: sweat a pad or not? spec isn't crystal clear on the 'legacy pad' and it sounds like it is just for some old crappy nics that have no business in a good server
} elsif ( $ self - > { 'ipmiversion' } eq '2.0' ) {
#TODO:
2010-01-28 03:42:24 +00:00
my $ size = scalar ( @ payload ) ;
2010-01-29 04:15:52 +00:00
if ( $ self - > { confalgo } ) {
my $ pad = ( $ size + 1 ) % 16 ;
if ( $ pad ) { $ pad = 16 - $ pad ; }
my $ newsize = $ size + $ pad + 17 ;
push @ msg , ( $ newsize & 0xff , $ newsize >> 8 ) ;
my @ iv ;
2010-01-29 04:40:10 +00:00
foreach ( 1 .. 16 ) { #generate a new iv for outbound packet
2010-01-29 04:16:34 +00:00
my $ num = int ( rand ( 255 ) ) ;
2010-01-29 04:15:52 +00:00
push @ msg , $ num ;
push @ iv , $ num ;
}
2010-01-30 01:06:16 +00:00
my $ cipher = Crypt::CBC - > new ( - literal_key = > 1 , - key = > $ self - > { aeskey } , - cipher = > "Rijndael" , - header = > "none" , - iv = > pack ( "C*" , @ iv ) , - keysize = > 16 , - padding = > \ & cbc_pad ) ;
push @ msg , ( unpack ( "C*" , $ cipher - > encrypt ( pack ( "C*" , @ payload ) ) ) ) ;
2010-01-29 04:15:52 +00:00
} else {
push @ msg , ( $ size & 0xff , $ size >> 8 ) ;
push @ msg , @ payload ;
}
2010-01-28 22:06:22 +00:00
if ( $ self - > { integrityalgo } ) {
2010-01-29 02:32:47 +00:00
my @ integdata = @ msg [ 4 .. ( scalar @ msg ) - 1 ] ;
my $ neededpad = ( ( scalar @ integdata ) + 2 ) % 4 ;
if ( $ neededpad ) { $ neededpad = 4 - $ neededpad ; }
for ( my $ i = 0 ; $ i < $ neededpad ; $ i + + ) {
push @ integdata , 0xff ;
push @ msg , 0xff ;
}
push @ msg , $ neededpad ;
push @ integdata , $ neededpad ;
push @ msg , 7 ;
push @ integdata , 7 ;
my $ intdata = pack ( "C*" , @ integdata ) ;
my @ acode = unpack ( "C*" , hmac_sha1 ( $ intdata , $ self - > { k1 } ) ) ;
push @ msg , splice @ acode , 0 , 12 ;
2010-01-27 14:58:18 +00:00
#push integrity pad
2010-01-28 03:42:24 +00:00
#push @msg,0x7; #reserved byte in 2.0
2010-01-27 14:58:18 +00:00
#push integrity data
2010-01-28 22:06:22 +00:00
}
2010-01-27 14:58:18 +00:00
}
2011-01-10 20:25:35 +00:00
while ( $ pendingpackets > $ maxpending ) { #if we hit our ceiling, wait until a slot frees up
$ self - > waitforrsp ( ) ;
}
2010-01-27 14:58:18 +00:00
$ socket - > send ( pack ( "C*" , @ msg ) , 0 , $ self - > { peeraddr } ) ;
2011-01-10 20:25:35 +00:00
$ pendingpackets += 1 ;
2010-01-27 14:58:18 +00:00
if ( $ self - > { sequencenumber } ) { #if using non-zero, increment, otherwise..
$ self - > { sequencenumber } += 1 ;
$ self - > { sequencenumberbytes } = [ $ self - > { sequencenumber } & 0xff , ( $ self - > { sequencenumber } >> 8 ) & 0xff , ( $ self - > { sequencenumber } >> 16 ) & 0xff , ( $ self - > { sequencenumber } >> 24 ) & 0xff ] ;
}
}
1 ;