ipmi_rs/connection/impls/rmcp/v1_5/
mod.rs

1use std::{net::UdpSocket, num::NonZeroU32};
2
3use crate::{
4    app::auth::{
5        ActivateSession, AuthError, AuthType, ChannelAuthenticationCapabilities,
6        GetSessionChallenge, PrivilegeLevel,
7    },
8    connection::{IpmiConnection, ParseResponseError, Request, Response},
9    Ipmi, IpmiError,
10};
11
12use super::{
13    internal::{validate_ipmb_checksums, IpmbState},
14    socket::RmcpIpmiSocket,
15    RmcpIpmiError, RmcpIpmiReceiveError, RmcpIpmiSendError,
16};
17
18pub use message::Message;
19
20mod auth;
21mod md2;
22#[cfg(feature = "md5")]
23mod md5;
24mod message;
25
26#[derive(Debug)]
27pub enum ActivationError {
28    Io(std::io::Error),
29    PasswordTooLong,
30    UsernameTooLong,
31    GetSessionChallenge(IpmiError<RmcpIpmiError, ParseResponseError<AuthError>>),
32    NoSupportedAuthenticationType,
33    ActivateSession(IpmiError<RmcpIpmiError, ParseResponseError<AuthError>>),
34}
35
36#[derive(Debug)]
37pub enum WriteError {
38    Io(std::io::Error),
39    /// A request was made to calculate the auth code for a message authenticated
40    /// using a method that requires a password, but no password was provided.
41    MissingPassword,
42    /// The payload length of for the V1_5 packet is larger than the maximum
43    /// allowed size (256 bytes).
44    PayloadTooLarge(usize),
45    /// The requested auth type is not supported.
46    UnsupportedAuthType(AuthType),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum ReadError {
51    /// There is not enough data in the packet to form a valid [`EncapsulatedMessage`].
52    NotEnoughData,
53    /// The auth type provided is not supported.
54    UnsupportedAuthType(u8),
55    /// There is a mismatch between the payload length field and the
56    /// actual length of the payload.
57    IncorrectPayloadLen,
58    /// The auth code of the message is not correct.
59    AuthcodeError,
60}
61
62pub struct State {
63    socket: RmcpIpmiSocket,
64    ipmb_state: IpmbState,
65    session_id: Option<NonZeroU32>,
66    auth_type: crate::app::auth::AuthType,
67    password: Option<[u8; 16]>,
68    session_sequence: u32,
69}
70
71impl core::fmt::Debug for State {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("State")
74            .field("socket", &self.socket)
75            .field("ipmb_state", &self.ipmb_state)
76            .field("session_id", &self.session_id)
77            .field("auth_type", &self.auth_type)
78            .field("password", &"<redacted>")
79            .field("session_sequence", &self.session_sequence)
80            .finish()
81    }
82}
83
84impl State {
85    pub fn new(socket: UdpSocket) -> Self {
86        Self {
87            socket: RmcpIpmiSocket::new(socket),
88            ipmb_state: Default::default(),
89            auth_type: AuthType::None,
90            password: None,
91            session_id: None,
92            session_sequence: 0,
93        }
94    }
95
96    pub fn release_socket(self) -> UdpSocket {
97        self.socket.release()
98    }
99
100    pub fn activate(
101        mut self,
102        authentication_caps: &ChannelAuthenticationCapabilities,
103        privilege_level: PrivilegeLevel,
104        username: Option<&str>,
105        password: Option<&[u8]>,
106    ) -> Result<Self, ActivationError> {
107        let password = if let Some(password) = password {
108            if password.len() > 16 {
109                return Err(ActivationError::PasswordTooLong);
110            } else {
111                let mut padded = [0u8; 16];
112                padded[..password.len()].copy_from_slice(password);
113                Some(padded)
114            }
115        } else {
116            None
117        };
118
119        self.password = password;
120
121        let mut ipmi = Ipmi::new(self);
122
123        log::debug!("Requesting challenge");
124
125        let challenge_command = match GetSessionChallenge::new(AuthType::None, username) {
126            Some(v) => v,
127            None => return Err(ActivationError::UsernameTooLong),
128        };
129
130        let challenge = match ipmi.send_recv(challenge_command) {
131            Ok(v) => v,
132            Err(e) => return Err(ActivationError::GetSessionChallenge(e)),
133        };
134
135        let activation_auth_type = authentication_caps
136            .best_auth()
137            .ok_or(ActivationError::NoSupportedAuthenticationType)?;
138
139        let activate_session: ActivateSession = ActivateSession {
140            auth_type: activation_auth_type,
141            maxiumum_privilege_level: privilege_level,
142            challenge_string: challenge.challenge_string,
143            initial_sequence_number: 0xDEAD_BEEF,
144        };
145
146        ipmi.inner_mut().session_id = Some(challenge.temporary_session_id);
147        ipmi.inner_mut().auth_type = activation_auth_type;
148
149        log::debug!("Activating session");
150
151        let activation_info = match ipmi.send_recv(activate_session.clone()) {
152            Ok(v) => v,
153            Err(e) => return Err(ActivationError::ActivateSession(e)),
154        };
155
156        log::debug!("Succesfully started a session ({:?})", activation_info);
157
158        self = ipmi.release();
159
160        self.session_sequence = activation_info.initial_sequence_number;
161        self.session_id = Some(activation_info.session_id);
162
163        assert_eq!(activate_session.auth_type, activation_auth_type);
164
165        Ok(self)
166    }
167}
168
169impl IpmiConnection for State {
170    type SendError = RmcpIpmiSendError;
171
172    type RecvError = RmcpIpmiReceiveError;
173
174    type Error = RmcpIpmiError;
175
176    fn send(&mut self, request: &mut Request) -> Result<(), RmcpIpmiSendError> {
177        log::trace!("Sending message with auth type {:?}", self.auth_type);
178
179        let request_sequence = &mut self.session_sequence;
180
181        // Only increment the request sequence once a session has been established
182        // succesfully.
183        if self.session_id.is_some() {
184            *request_sequence = request_sequence.wrapping_add(1);
185        }
186
187        let final_data = super::internal::next_ipmb_message(request, &mut self.ipmb_state);
188
189        let message = Message {
190            auth_type: self.auth_type,
191            session_sequence_number: self.session_sequence,
192            session_id: self.session_id.map(|v| v.get()).unwrap_or(0),
193            payload: final_data,
194        };
195
196        enum Send {
197            Ipmi(WriteError),
198            Io(std::io::Error),
199        }
200
201        impl From<std::io::Error> for Send {
202            fn from(value: std::io::Error) -> Self {
203                Self::Io(value)
204            }
205        }
206
207        match self.socket.send(|buffer| {
208            message
209                .write_data(self.password.as_ref(), buffer)
210                .map_err(Send::Ipmi)
211        }) {
212            Ok(_) => Ok(()),
213            Err(Send::Ipmi(ipmi)) => Err(RmcpIpmiSendError::V1_5(ipmi)),
214            Err(Send::Io(io)) => Err(RmcpIpmiSendError::V1_5(WriteError::Io(io))),
215        }
216    }
217
218    fn recv(&mut self) -> Result<Response, RmcpIpmiReceiveError> {
219        let data = self.socket.recv()?;
220
221        let data = Message::from_data(self.password.as_ref(), data)
222            .map_err(|e| RmcpIpmiReceiveError::Session(super::UnwrapSessionError::V1_5(e)))?
223            .payload;
224
225        if data.len() < 7 {
226            return Err(RmcpIpmiReceiveError::NotEnoughData);
227        }
228
229        let _req_addr = data[0];
230        let netfn = data[1] >> 2;
231        let _checksum1 = data[2];
232        let _rs_addr = data[3];
233        let _rqseq = data[4];
234        let cmd = data[5];
235        let response_data: Vec<_> = data[6..data.len() - 1].to_vec();
236        let _checksum2 = data[data.len() - 1];
237
238        if !validate_ipmb_checksums(&data) {
239            return Err(RmcpIpmiReceiveError::IpmbChecksumFailed);
240        }
241
242        // TODO: validate sequence number
243
244        if let Some(resp) = Response::new(
245            crate::connection::Message::new_raw(netfn, cmd, response_data),
246            0,
247        ) {
248            Ok(resp)
249        } else {
250            // TODO: need better message here :)
251            Err(RmcpIpmiReceiveError::EmptyMessage)
252        }
253    }
254
255    fn send_recv(&mut self, request: &mut Request) -> Result<Response, Self::Error> {
256        self.send(request)?;
257        let response = self.recv()?;
258        Ok(response)
259    }
260}