Skip to content

Commit bd750bd

Browse files
authored
Merge pull request #31 from AvivNaaman/27-support-smb-302
27 support smb 302
2 parents 672a4a9 + 5a0604a commit bd750bd

File tree

22 files changed

+1102
-538
lines changed

22 files changed

+1102
-538
lines changed

Cargo.lock

Lines changed: 336 additions & 150 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

smb-cli/src/path.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
use crate::cli::Cli;
22
use maybe_async::*;
33
use smb::{
4-
connection::Connection, packets::smb2::*, resource::Resource, session::Session, tree::Tree,
4+
connection::{Connection, EncryptionMode},
5+
packets::smb2::*,
6+
resource::Resource,
7+
session::Session,
8+
tree::Tree,
9+
ConnectionConfig,
510
};
611
use std::{error::Error, str::FromStr};
712

@@ -18,7 +23,11 @@ impl UncPath {
1823
&self,
1924
cli: &Cli,
2025
) -> Result<(Connection, Session, Tree, Option<Resource>), Box<dyn Error>> {
21-
let mut smb = Connection::new();
26+
let mut smb = Connection::build(ConnectionConfig {
27+
max_dialect: Some(Dialect::MAX),
28+
encryption_mode: EncryptionMode::Allowed,
29+
..Default::default()
30+
})?;
2231
smb.set_timeout(Some(std::time::Duration::from_secs(10)))
2332
.await?;
2433
smb.connect(format!("{}:{}", self.server, cli.port).as_str())

smb/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ lz4_flex = { version = "0.11", default-features = false, features = [
5050

5151
# Dev - Tests
5252
[dev-dependencies]
53-
env_logger = "0.11"
53+
test-log = "0.2"
54+
serial_test = "3.2"
5455

5556
[features]
5657
default = ["sign", "encrypt", "compress"]

smb/src/connection.rs

Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
pub mod config;
12
pub mod negotiation_state;
23
pub mod netbios_client;
34
pub mod preauth_hash;
45
pub mod transformer;
56
pub mod worker;
67

8+
use crate::dialects::get_dialect_impl;
79
use crate::packets::guid::Guid;
810
use crate::packets::smb2::{Command, Message};
911
use crate::Error;
@@ -19,8 +21,9 @@ use crate::{
1921
session::Session,
2022
};
2123
use binrw::prelude::*;
24+
pub use config::*;
2225
use maybe_async::*;
23-
use negotiation_state::NegotiateState;
26+
use negotiation_state::{ConnectionInfo, NegotiatedProperties};
2427
use netbios_client::NetBiosClient;
2528
use std::cmp::max;
2629
use std::sync::atomic::{AtomicU16, AtomicU64};
@@ -31,20 +34,21 @@ use worker::{Worker, WorkerImpl};
3134

3235
pub struct Connection {
3336
handler: HandlerReference<ConnectionMessageHandler>,
34-
timeout: Option<std::time::Duration>,
37+
config: ConnectionConfig,
3538
}
3639

3740
impl Connection {
38-
pub fn new() -> Connection {
39-
Connection {
41+
pub fn build(config: ConnectionConfig) -> crate::Result<Connection> {
42+
config.validate()?;
43+
Ok(Connection {
4044
handler: HandlerReference::new(ConnectionMessageHandler::new()),
41-
timeout: None,
42-
}
45+
config,
46+
})
4347
}
4448

4549
#[maybe_async]
4650
pub async fn set_timeout(&mut self, timeout: Option<Duration>) -> crate::Result<()> {
47-
self.timeout = timeout;
51+
self.config.timeout = timeout;
4852
if let Some(worker) = self.handler.worker.get() {
4953
worker.set_timeout(timeout).await?;
5054
}
@@ -53,7 +57,7 @@ impl Connection {
5357

5458
#[maybe_async]
5559
pub async fn connect(&mut self, address: &str) -> crate::Result<()> {
56-
let mut netbios_client = NetBiosClient::new(self.timeout);
60+
let mut netbios_client = NetBiosClient::new(self.config.timeout);
5761

5862
log::debug!("Connecting to {}...", address);
5963
netbios_client.connect(address).await?;
@@ -118,26 +122,48 @@ impl Connection {
118122
}
119123
}
120124

121-
Ok(WorkerImpl::start(netbios_client, self.timeout).await?)
125+
Ok(WorkerImpl::start(netbios_client, self.config.timeout).await?)
122126
}
123127

124128
#[maybe_async]
125129
async fn negotiate_smb2(&mut self) -> crate::Result<()> {
126130
// Confirm that we're not already negotiated.
127-
if self.handler.negotiate_state().is_some() {
131+
if self.handler.negotiate_info().is_some() {
128132
return Err(Error::InvalidState("Already negotiated".into()));
129133
}
130134

131135
log::debug!("Negotiating SMB2");
132136

137+
// List possible versions to run with.
138+
let min_dialect = self.config.min_dialect.unwrap_or(Dialect::MIN);
139+
let max_dialect = self.config.max_dialect.unwrap_or(Dialect::MAX);
140+
let dialects: Vec<Dialect> = Dialect::ALL
141+
.iter()
142+
.filter(|dialect| **dialect >= min_dialect && **dialect <= max_dialect)
143+
.copied()
144+
.collect();
145+
146+
if dialects.is_empty() {
147+
return Err(Error::InvalidConfiguration(
148+
"No dialects to negotiate".to_string(),
149+
));
150+
}
151+
152+
let encryption_algos = if !self.config.encryption_mode.is_disabled() {
153+
crypto::SIGNING_ALGOS.into()
154+
} else {
155+
vec![]
156+
};
157+
133158
// Send SMB2 negotiate request
134159
let client_guid = self.handler.client_guid;
135160
let response = self
136161
.handler
137162
.send_recv(Content::NegotiateRequest(NegotiateRequest::new(
138163
"AVIV-MBP".to_string(),
139164
client_guid,
140-
crypto::SIGNING_ALGOS.into(),
165+
dialects.clone(),
166+
encryption_algos,
141167
crypto::ENCRYPTING_ALGOS.to_vec(),
142168
compression::SUPPORTED_ALGORITHMS.to_vec(),
143169
)))
@@ -152,72 +178,55 @@ impl Connection {
152178
))?;
153179

154180
// well, only 3.1 is supported for starters.
155-
if smb2_negotiate_response.dialect_revision != NegotiateDialect::Smb0311 {
156-
return Err(Error::UnsupportedDialect(
157-
smb2_negotiate_response.dialect_revision,
158-
));
159-
}
160-
161-
if let None = smb2_negotiate_response.negotiate_context_list {
162-
return Err(Error::InvalidMessage(
163-
"Expected negotiate context list".to_string(),
181+
if !dialects.contains(&smb2_negotiate_response.dialect_revision.try_into()?) {
182+
return Err(Error::NegotiationError(
183+
"Server selected an unsupported dialect.".into(),
164184
));
165185
}
166186

167-
let signing_algo = if let Some(signing_algo) = smb2_negotiate_response.get_signing_algo() {
168-
if !crypto::SIGNING_ALGOS.contains(&signing_algo) {
169-
return Err(Error::NegotiationError(
170-
"Unsupported signing algorithm selected!".into(),
171-
));
172-
}
173-
Some(signing_algo)
174-
} else {
175-
None
176-
};
177-
178-
// Make sure preauth integrity capability is SHA-512, if it exists in response:
179-
if let Some(algo) = smb2_negotiate_response.get_preauth_integrity_algo() {
180-
if !preauth_hash::SUPPORTED_ALGOS.contains(&algo) {
181-
return Err(Error::NegotiationError(
182-
"Unsupported preauth integrity algorithm received".into(),
183-
));
184-
}
185-
}
186-
187-
// And verify that the encryption algorithm is supported.
188-
let encryption_cipher = smb2_negotiate_response.get_encryption_cipher();
189-
if let Some(encryption_cipher) = &encryption_cipher {
190-
if !crypto::ENCRYPTING_ALGOS.contains(&encryption_cipher) {
191-
return Err(Error::NegotiationError(
192-
"Unsupported encryption algorithm received".into(),
193-
));
194-
}
195-
}
196-
197-
let compression: Option<CompressionCaps> = match smb2_negotiate_response.get_compression() {
198-
Some(compression) => Some(compression.clone()),
199-
None => None,
200-
};
201-
202-
let negotiate_state = NegotiateState {
187+
let dialect_rev = smb2_negotiate_response.dialect_revision.try_into()?;
188+
let dialect_impl = get_dialect_impl(&dialect_rev);
189+
let mut state = NegotiatedProperties {
203190
server_guid: smb2_negotiate_response.server_guid,
204-
global_caps: smb2_negotiate_response.capabilities.clone(),
191+
caps: smb2_negotiate_response.capabilities.clone(),
205192
max_transact_size: smb2_negotiate_response.max_transact_size,
206193
max_read_size: smb2_negotiate_response.max_read_size,
207194
max_write_size: smb2_negotiate_response.max_write_size,
208-
gss_token: smb2_negotiate_response.buffer,
209-
selected_dialect: smb2_negotiate_response.dialect_revision.try_into()?,
210-
signing_algo,
211-
encryption_cipher,
212-
compression,
195+
gss_token: smb2_negotiate_response.buffer.clone(),
196+
signing_algo: None,
197+
encryption_cipher: None,
198+
compression: None,
199+
dialect_rev,
213200
};
201+
202+
dialect_impl.process_negotiate_request(
203+
&smb2_negotiate_response,
204+
&mut state,
205+
&self.config,
206+
)?;
207+
if ((!u32::from_le_bytes(dialect_impl.get_negotiate_caps_mask().into_bytes()))
208+
& u32::from_le_bytes(state.caps.into_bytes()))
209+
!= 0
210+
{
211+
return Err(Error::NegotiationError(
212+
"Server capabilities are invalid for the selected dialect.".into(),
213+
));
214+
}
215+
214216
log::trace!(
215217
"Negotiated SMB results: dialect={:?}, state={:?}",
216-
negotiate_state.selected_dialect,
217-
&negotiate_state
218+
dialect_rev,
219+
&state
218220
);
219221

220-
self.handler.negotiate_state.set(negotiate_state).unwrap();
222+
self.handler
223+
.negotiate_info
224+
.set(ConnectionInfo {
225+
state,
226+
dialect: dialect_impl,
227+
config: self.config.clone(),
228+
})
229+
.unwrap();
221230

222231
Ok(())
223232
}
@@ -229,7 +238,7 @@ impl Connection {
229238
netbios_client: NetBiosClient,
230239
multi_protocol: bool,
231240
) -> crate::Result<()> {
232-
if self.handler.negotiate_state().is_some() {
241+
if self.handler.negotiate_info().is_some() {
233242
return Err(Error::InvalidState("Already negotiated".into()));
234243
}
235244

@@ -247,7 +256,7 @@ impl Connection {
247256
.get()
248257
.ok_or("Worker is uninitialized")
249258
.unwrap()
250-
.negotaite_complete(&self.handler.negotiate_state().unwrap())
259+
.negotaite_complete(&self.handler.negotiate_info().unwrap())
251260
.await;
252261
log::info!("Negotiation successful");
253262
Ok(())
@@ -277,7 +286,7 @@ pub struct ConnectionMessageHandler {
277286
worker: OnceCell<Arc<WorkerImpl>>,
278287

279288
// Negotiation-related state.
280-
negotiate_state: OnceCell<NegotiateState>,
289+
negotiate_info: OnceCell<ConnectionInfo>,
281290

282291
/// Number of credits available to the client at the moment, for the next requests.
283292
curr_credits: Semaphore,
@@ -292,16 +301,16 @@ impl ConnectionMessageHandler {
292301
ConnectionMessageHandler {
293302
client_guid: Guid::gen(),
294303
worker: OnceCell::new(),
295-
negotiate_state: OnceCell::new(),
304+
negotiate_info: OnceCell::new(),
296305
extra_credits_to_request: 4,
297306
curr_credits: Semaphore::new(1),
298307
curr_msg_id: AtomicU64::new(1),
299308
credit_pool: AtomicU16::new(1),
300309
}
301310
}
302311

303-
pub fn negotiate_state(&self) -> Option<&NegotiateState> {
304-
self.negotiate_state.get()
312+
pub fn negotiate_info(&self) -> Option<&ConnectionInfo> {
313+
self.negotiate_info.get()
305314
}
306315

307316
pub fn worker(&self) -> Option<&Arc<WorkerImpl>> {
@@ -319,8 +328,8 @@ impl ConnectionMessageHandler {
319328

320329
#[maybe_async]
321330
async fn process_sequence_outgoing(&self, msg: &mut OutgoingMessage) -> crate::Result<()> {
322-
if let Some(neg) = self.negotiate_state() {
323-
if neg.selected_dialect > Dialect::Smb0202 && neg.global_caps.large_mtu() {
331+
if let Some(neg) = self.negotiate_info() {
332+
if neg.state.dialect_rev > Dialect::Smb0202 && neg.state.caps.large_mtu() {
324333
// Calculate the cost of the message (charge).
325334
let cost = if Self::SET_CREDIT_CHARGE_CMDS
326335
.iter()
@@ -370,8 +379,8 @@ impl ConnectionMessageHandler {
370379

371380
#[maybe_async]
372381
async fn process_sequence_incoming(&self, msg: &IncomingMessage) -> crate::Result<()> {
373-
if let Some(neg) = self.negotiate_state() {
374-
if neg.selected_dialect > Dialect::Smb0202 && neg.global_caps.large_mtu() {
382+
if let Some(neg) = self.negotiate_info() {
383+
if neg.state.dialect_rev > Dialect::Smb0202 && neg.state.caps.large_mtu() {
375384
let granted_credits = msg.message.header.credit_request;
376385
let charged_credits = msg.message.header.credit_charge;
377386
// Update the pool size - return how many EXTRA credits were granted.
@@ -400,8 +409,8 @@ impl MessageHandler for ConnectionMessageHandler {
400409
#[maybe_async]
401410
async fn sendo(&self, mut msg: OutgoingMessage) -> crate::Result<SendMessageResult> {
402411
// TODO: Add assertion in the struct regarding the selected dialect!
403-
let priority_value = match self.negotiate_state.get() {
404-
Some(negotiate_state) => match negotiate_state.selected_dialect {
412+
let priority_value = match self.negotiate_info.get() {
413+
Some(neg_info) => match neg_info.state.dialect_rev {
405414
Dialect::Smb0311 => 1,
406415
_ => 0,
407416
},

smb/src/connection/config.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::time::Duration;
2+
3+
use crate::packets::smb2::Dialect;
4+
5+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
6+
pub enum EncryptionMode {
7+
/// Encryption is allowed but not required, it's up to the server to decide.
8+
#[default]
9+
Allowed,
10+
/// Encryption is required, and connection will fail if the server does not support it.
11+
Required,
12+
/// Encryption is disabled, server might fail the connection if it requires encryption.
13+
Disabled,
14+
}
15+
16+
impl EncryptionMode {
17+
/// Returns true if encryption is required.
18+
pub fn is_required(&self) -> bool {
19+
matches!(self, Self::Required)
20+
}
21+
22+
/// Returns true if encryption is disabled.
23+
pub fn is_disabled(&self) -> bool {
24+
matches!(self, Self::Disabled)
25+
}
26+
}
27+
28+
/// Specifies the configuration for a connection.
29+
#[derive(Debug, Default, Clone)]
30+
pub struct ConnectionConfig {
31+
pub timeout: Option<Duration>,
32+
pub min_dialect: Option<Dialect>,
33+
pub max_dialect: Option<Dialect>,
34+
pub encryption_mode: EncryptionMode,
35+
}
36+
37+
impl ConnectionConfig {
38+
/// Validates the configuration.
39+
pub fn validate(&self) -> crate::Result<()> {
40+
// Make sure dialects min <= max.
41+
if let (Some(min), Some(max)) = (self.min_dialect, self.max_dialect) {
42+
if min > max {
43+
return Err(crate::Error::InvalidConfiguration(
44+
"Minimum dialect is greater than maximum dialect".to_string(),
45+
));
46+
}
47+
}
48+
Ok(())
49+
}
50+
}

0 commit comments

Comments
 (0)