Skip to content

Commit a2e0d4b

Browse files
committed
new: replaced pavao with pure rust smb crate (fixes #78)
1 parent e43ef6e commit a2e0d4b

8 files changed

Lines changed: 1228 additions & 199 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ rdp-rs = { version = "0.1.0", optional = true }
8383
scylla = { version = "0.10.1", optional = true }
8484
paho-mqtt = { version = "0.13.3", optional = true }
8585
csv = "1.3.0"
86-
pavao = { version = "0.2.12", optional = true }
86+
smb = { version = "0.8.2", optional = true }
8787
fast-socks5 = { version = "0.9.2", optional = true }
8888
shell-words = "1.1.0"
8989
serde_yaml = "0.9.30"
@@ -158,7 +158,7 @@ amqp = []
158158
redis = []
159159
scylla = ["dep:scylla"]
160160
port_scanner = ["dep:reqwest"]
161-
samba = ["dep:pavao"]
161+
samba = ["dep:smb"]
162162
socks5 = ["dep:fast-socks5"]
163163

164164
# used to build for platforms without openssl

Cross.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ zig = false
55
default-target = "x86_64-unknown-linux-gnu"
66
pre-build = [
77
"dpkg --add-architecture $CROSS_DEB_ARCH",
8-
"apt-get update && apt-get --assume-yes install pkg-config:$CROSS_DEB_ARCH libssl-dev:$CROSS_DEB_ARCH libsmbclient-dev:$CROSS_DEB_ARCH cmake git",
8+
"apt-get update && apt-get --assume-yes install pkg-config:$CROSS_DEB_ARCH libssl-dev:$CROSS_DEB_ARCH cmake git",
99
]

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
FROM rust:bullseye AS builder
22

3-
RUN apt-get update && apt-get install -y libsmbclient-dev libssl-dev ca-certificates cmake git
3+
RUN apt-get update && apt-get install -y libssl-dev ca-certificates cmake git
44

55
WORKDIR /app
66
ADD . /app
77
RUN cargo build --release
88

99
FROM debian:bullseye
10-
RUN apt-get update && apt-get install -y libsmbclient libssl-dev ca-certificates
10+
RUN apt-get update && apt-get install -y libssl-dev ca-certificates
1111
COPY --from=builder /app/target/release/legba /usr/bin/legba
1212
ENTRYPOINT ["/usr/bin/legba"]

src/main.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,21 @@ fn setup() -> Result<Options, session::Error> {
3232
unsafe {
3333
env::set_var(
3434
"RUST_LOG",
35-
"info,blocking=off,pavao=off,fast_socks5=off,actix_server=warn,rmcp=warn",
35+
"info,blocking=off,pavao=off,fast_socks5=off,actix_server=warn,rmcp=warn,smb=warn,sspi=off",
3636
);
3737
}
3838
}
3939

40-
env_logger::builder()
41-
.format_module_path(false)
42-
.format_target(false)
43-
.format_timestamp(None)
44-
.target(Target::Stdout)
45-
.init();
40+
if env::var_os("RUST_LOG") == Some("debug".into()) {
41+
env_logger::builder().target(Target::Stdout).init();
42+
} else {
43+
env_logger::builder()
44+
.format_module_path(false)
45+
.format_target(false)
46+
.format_timestamp(None)
47+
.target(Target::Stdout)
48+
.init();
49+
}
4650

4751
let mut options: Options = Options::parse();
4852

src/options.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clap::{parser::ValueSource, ArgMatches, CommandFactory as _, Parser};
1+
use clap::{ArgMatches, CommandFactory as _, Parser, parser::ValueSource};
22
use serde::{Deserialize, Serialize};
33
use serde_json::Value;
44

@@ -118,9 +118,6 @@ pub(crate) struct Options {
118118
#[cfg(feature = "telnet")]
119119
#[clap(flatten, next_help_heading = "TELNET")]
120120
pub telnet: crate::plugins::telnet::options::Options,
121-
#[cfg(feature = "samba")]
122-
#[clap(flatten, next_help_heading = "SAMBA (SMB)")]
123-
pub smb: crate::plugins::samba::options::Options,
124121
#[cfg(feature = "ssh")]
125122
#[clap(flatten, next_help_heading = "SSH")]
126123
pub ssh: crate::plugins::ssh::options::Options,
@@ -159,9 +156,11 @@ pub(crate) struct Options {
159156
pub irc: crate::plugins::irc::options::Options,
160157
}
161158

162-
163-
164-
fn update_field_by_name(options: &mut Options, field_name: &str, matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
159+
fn update_field_by_name(
160+
options: &mut Options,
161+
field_name: &str,
162+
matches: &ArgMatches,
163+
) -> Result<(), Box<dyn std::error::Error>> {
165164
// Serialize current options to JSON
166165
let mut options_value: Value = serde_json::to_value(&options)?;
167166

@@ -194,12 +193,21 @@ fn update_field_by_name(options: &mut Options, field_name: &str, matches: &ArgMa
194193
return Err(format!("Missing value for field: {:?}", &field_name).into());
195194
}
196195
}
197-
_ => return Err(format!("Unknown or unsupported type for field: {:?} (current_value={:?})", &field_name, &current_value).into())
196+
_ => {
197+
return Err(format!(
198+
"Unknown or unsupported type for field: {:?} (current_value={:?})",
199+
&field_name, &current_value
200+
)
201+
.into());
202+
}
198203
}
199204
}
200-
201205
} else {
202-
return Err(format!("Invalid options structure: expected JSON object for field: {:?}", &field_name).into());
206+
return Err(format!(
207+
"Invalid options structure: expected JSON object for field: {:?}",
208+
&field_name
209+
)
210+
.into());
203211
}
204212

205213
// Deserialize back to Options
@@ -208,18 +216,26 @@ fn update_field_by_name(options: &mut Options, field_name: &str, matches: &ArgMa
208216
Ok(())
209217
}
210218

211-
pub(crate) fn update_selectively(options: &mut Options, argv: &[String]) -> Result<(), Box<dyn std::error::Error>> {
219+
pub(crate) fn update_selectively(
220+
options: &mut Options,
221+
argv: &[String],
222+
) -> Result<(), Box<dyn std::error::Error>> {
212223
let cmd = Options::command();
213224
let matches = cmd.clone().try_get_matches_from(argv)?;
214-
225+
215226
// Update only the fields that were explicitly provided
216227
for arg in cmd.get_arguments() {
217228
let id = arg.get_id().as_str();
218-
if matches.contains_id(id) &&
219-
matches.value_source(id) == Some(ValueSource::CommandLine) && id != "plugin" && id != "P" && id != "R" && id != "recipe" {
229+
if matches.contains_id(id)
230+
&& matches.value_source(id) == Some(ValueSource::CommandLine)
231+
&& id != "plugin"
232+
&& id != "P"
233+
&& id != "R"
234+
&& id != "recipe"
235+
{
220236
update_field_by_name(options, id, &matches)?;
221237
}
222238
}
223-
239+
224240
Ok(())
225241
}

src/plugins/samba/mod.rs

Lines changed: 31 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,22 @@
1-
use std::collections::HashMap;
2-
use std::sync::LazyLock;
31
use std::time::Duration;
42

53
use async_trait::async_trait;
6-
use pavao::{SmbClient, SmbCredentials, SmbDirentType, SmbOptions};
7-
use tokio::sync::Mutex;
84

5+
use crate::Plugin;
96
use crate::creds::Credentials;
107
use crate::session::{Error, Loot};
11-
use crate::Plugin;
12-
use crate::{utils, Options};
13-
14-
pub(crate) mod options;
15-
16-
static SHARE_CACHE: LazyLock<Mutex<HashMap<String, String>>> =
17-
LazyLock::new(|| Mutex::new(HashMap::new()));
18-
static PAVAO_LOCK: Mutex<()> = Mutex::const_new(());
8+
use crate::{Options, utils};
199

2010
super::manager::register_plugin! {
2111
"smb" => SMB::new()
2212
}
2313

2414
#[derive(Clone)]
25-
pub(crate) struct SMB {
26-
share: Option<String>,
27-
workgroup: String,
28-
}
15+
pub(crate) struct SMB {}
2916

3017
impl SMB {
3118
pub fn new() -> Self {
32-
SMB {
33-
share: None,
34-
workgroup: String::default(),
35-
}
36-
}
37-
38-
fn get_samba_client(
39-
&self,
40-
server: &str,
41-
workgroup: &str,
42-
share: &str,
43-
username: &str,
44-
password: &str,
45-
) -> Result<SmbClient, Error> {
46-
SmbClient::new(
47-
SmbCredentials::default()
48-
.server(server)
49-
.share(share)
50-
.username(username)
51-
.password(password)
52-
.workgroup(workgroup),
53-
SmbOptions::default()
54-
.no_auto_anonymous_login(false)
55-
.one_share_per_server(true),
56-
)
57-
.map_err(|e| format!("error creating client for {}: {}", share, e))
58-
}
59-
60-
async fn get_share_for(&self, target: &str) -> Result<String, Error> {
61-
if let Some(share) = self.share.as_ref() {
62-
// return from arguments
63-
return Ok(share.clone());
64-
}
65-
66-
let mut guard = SHARE_CACHE.lock().await;
67-
if let Some(share) = guard.get(target) {
68-
// return from cache
69-
return Ok(share.clone());
70-
}
71-
72-
// get from listing
73-
log::info!("searching private share for {} ...", target);
74-
75-
let server = format!("smb://{}", target);
76-
let root_cli = self.get_samba_client(&server, &self.workgroup, "", "", "")?;
77-
78-
if let Ok(entries) = root_cli.list_dir("") {
79-
for entry in entries {
80-
match entry.get_type() {
81-
SmbDirentType::FileShare | SmbDirentType::Dir => {
82-
let share = format!("/{}", entry.name());
83-
// if share is private we expect an error
84-
let sub_cli =
85-
self.get_samba_client(&server, &self.workgroup, &share, "", "")?;
86-
let listing = sub_cli.list_dir("");
87-
if listing.is_err() {
88-
log::info!("{}{} found", &server, &share);
89-
// found a private share, update the cache and return.
90-
guard.insert(target.to_owned(), share.clone());
91-
return Ok(share);
92-
}
93-
}
94-
_ => {}
95-
}
96-
}
97-
}
98-
99-
Err(format!(
100-
"could not find private share for {}, provide one with --smb-share",
101-
target
102-
))
19+
SMB {}
10320
}
10421
}
10522

@@ -109,9 +26,7 @@ impl Plugin for SMB {
10926
"Samba password authentication."
11027
}
11128

112-
async fn setup(&mut self, opts: &Options) -> Result<(), Error> {
113-
self.share = opts.smb.smb_share.clone();
114-
self.workgroup = opts.smb.smb_workgroup.clone();
29+
async fn setup(&mut self, _: &Options) -> Result<(), Error> {
11530
Ok(())
11631
}
11732

@@ -120,36 +35,41 @@ impl Plugin for SMB {
12035
creds: &Credentials,
12136
timeout: Duration,
12237
) -> Result<Option<Vec<Loot>>, Error> {
123-
let address = utils::parse_target_address(&creds.target, 445)?;
124-
let server = format!("smb://{}", &address);
125-
let share = tokio::time::timeout(timeout, self.get_share_for(&address))
126-
.await
127-
.map_err(|e: tokio::time::error::Elapsed| e.to_string())?
38+
let (address, port) = utils::parse_target(&creds.target, 445)?;
39+
40+
let mut config = smb::ClientConfig::default();
41+
42+
config.connection.port = Some(port);
43+
config.connection.timeout = Some(timeout);
44+
45+
let mut conn = smb::Connection::build(&address, config.connection.clone())
12846
.map_err(|e| e.to_string())?;
12947

130-
// HACK: pavao doesn't seem to be thread safe, so we need to acquire this lock here.
131-
// Sadly this decreases performances, but it appears that there are no alternatives
132-
// for rust :/
133-
let _guard = PAVAO_LOCK.lock().await;
134-
let client = self.get_samba_client(
135-
&server,
136-
&self.workgroup,
137-
&share,
138-
&creds.username,
139-
&creds.password,
140-
)?;
48+
conn.connect().await.map_err(|e| e.to_string())?;
14149

142-
return if client.list_dir("/").is_ok() {
143-
Ok(Some(vec![Loot::new(
50+
return match conn
51+
.authenticate(&creds.username, creds.password.clone())
52+
.await
53+
{
54+
Ok(_) => Ok(Some(vec![Loot::new(
14455
"smb",
14556
&address,
14657
[
14758
("username".to_owned(), creds.username.to_owned()),
14859
("password".to_owned(), creds.password.to_owned()),
14960
],
150-
)]))
151-
} else {
152-
Ok(None)
61+
)])),
62+
// correct user, wrong pass: Some(UnexpectedMessageStatus(3221225581))
63+
Err(smb::Error::UnexpectedMessageStatus(_)) => Ok(Some(vec![
64+
Loot::new(
65+
"smb",
66+
&address,
67+
[("username".to_owned(), creds.username.to_owned())],
68+
)
69+
.set_partial(),
70+
])),
71+
// wrong user: Some(InvalidMessage("Message not signed or encrypted, but signing is required for the session!"))
72+
Err(_) => Ok(None),
15373
};
15474
}
15575
}

src/plugins/samba/options.rs

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)