ipmi_rs/connection/impls/rmcp/v1_5/
mod.rs1use 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 MissingPassword,
42 PayloadTooLarge(usize),
45 UnsupportedAuthType(AuthType),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum ReadError {
51 NotEnoughData,
53 UnsupportedAuthType(u8),
55 IncorrectPayloadLen,
58 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 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 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 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}