use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser)]
pub(crate) struct MssqlArgs {
#[command(flatten)]
pub auth: crate::shared_args::SmbAuth,
#[arg(long)]
pub query: String,
#[arg(long, default_value_t = 1433)]
pub port: u16,
#[arg(long)]
pub database: Option<String>,
#[arg(long)]
pub tsv: bool,
#[arg(long, value_delimiter = ',')]
pub execute_as: Vec<String>,
}
pub(crate) async fn mssql(mut a: MssqlArgs) -> Result<()> {
a.auth.password = crate::resolve_secret(&a.auth.password, "ADHAMMER_PASSWORD")?;
let mut c = ms_tds::TdsClient::connect(&a.auth.host, a.port, None)
.await
.with_context(|| format!("TDS connect {}:{}", a.auth.host, a.port))?;
tracing::info!(
target: "adhammer::mssql",
"TDS session up (encrypted={})",
c.is_encrypted()
);
c.login_ntlm(&a.auth.domain, &a.auth.user, &a.auth.password)
.await
.with_context(|| {
format!(
"NTLM login {}\\{} @ {}:{}",
a.auth.domain, a.auth.user, a.auth.host, a.port
)
})?;
tracing::info!(target: "adhammer::mssql", "NTLM login OK");
if let Some(db) = &a.database {
let use_sql = format!("USE [{}]", db.replace(']', "]]"));
let rs = c
.run_query(&use_sql)
.await
.with_context(|| format!("USE {db}"))?;
report_result(&format!("USE {db}"), &rs, a.tsv);
}
let mut stacked = 0usize;
for principal in &a.execute_as {
if let Err(e) = c.impersonate(principal).await {
eprintln!("[-] EXECUTE AS LOGIN='{principal}' failed: {e:#}");
revert_stack(&mut c, stacked).await;
return Err(e).with_context(|| format!("EXECUTE AS LOGIN='{principal}'"));
}
stacked += 1;
eprintln!("[+] EXECUTE AS LOGIN='{principal}' pushed (frame {stacked})");
}
let query_result = c.run_query(&a.query).await;
revert_stack(&mut c, stacked).await;
match query_result {
Ok(rs) => {
report_result(&a.query, &rs, a.tsv);
Ok(())
}
Err(e) => Err(e).context("user query"),
}
}
async fn revert_stack(c: &mut ms_tds::TdsClient, n: usize) {
for i in (1..=n).rev() {
match c.revert_to_self().await {
Ok(()) => eprintln!("[+] REVERT (frame {i})"),
Err(e) => eprintln!("[!] REVERT (frame {i}) failed: {e:#}"),
}
}
}
fn report_result(label: &str, rs: &ms_tds::ResultSet, tsv: bool) {
if tsv {
for m in &rs.messages {
println!("msg\t{m}");
}
println!("done\t{}", rs.row_count);
return;
}
println!("== {label} ==");
if rs.messages.is_empty() {
println!(" (no server messages)");
} else {
for m in &rs.messages {
println!(" msg: {m}");
}
}
println!(" rows: {}", rs.row_count);
if rs.row_count > 0 && rs.rows.is_empty() {
println!(
" note: {} row(s) returned but per-column values not decoded (ms-tds ROW decoder pending)",
rs.row_count
);
}
}