use std::borrow::Cow;
use std::io;
use std::path::PathBuf;
use std::time::Duration;
use windows::Win32::Foundation::{
ERROR_IO_PENDING, ERROR_OPERATION_ABORTED, ERROR_PIPE_CONNECTED, GetLastError, WAIT_FAILED,
WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows::Win32::Storage::FileSystem::{
FILE_FLAG_OVERLAPPED, FILE_FLAGS_AND_ATTRIBUTES, PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND,
PIPE_ACCESS_OUTBOUND, ReadFile, WriteFile,
};
use windows::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED};
use windows::Win32::System::Pipes::{
ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe, GetNamedPipeClientProcessId,
NAMED_PIPE_MODE, PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE, PIPE_REJECT_REMOTE_CLIENTS,
PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT,
};
use windows::Win32::System::Threading::WaitForMultipleObjects;
use windows::core::PCWSTR;
use crate::error::{
AccessDeniedError, InvalidParameterError, PipeConnectError, PipeError, PipeTimeoutError,
};
use crate::process::{Process, ProcessId};
use crate::utils::to_utf16_nul;
use crate::wait::Wait;
use crate::{Error, Result};
use super::error_map::map_pipe_windows_error;
use super::security_attrs::NativePipeSecurityAttributes;
use super::types::{
NamedPipeOpenMode, NamedPipeType, PipeName, PipeSecurityOptions, PipeServerEndpoint,
};
#[derive(Debug, Clone)]
pub struct NamedPipeServerBuilder {
pipe_name: Option<PipeName>,
open_mode: NamedPipeOpenMode,
pipe_type: NamedPipeType,
max_instances: u8,
out_buffer_size: u32,
in_buffer_size: u32,
default_timeout: Duration,
security: PipeSecurityOptions,
allowed_executables: Vec<PathBuf>,
}
impl NamedPipeServerBuilder {
pub fn new() -> Self {
Self {
pipe_name: None,
open_mode: NamedPipeOpenMode::Duplex,
pipe_type: NamedPipeType::Byte,
max_instances: 1,
out_buffer_size: 4096,
in_buffer_size: 4096,
default_timeout: Duration::from_secs(5),
security: PipeSecurityOptions::default(),
allowed_executables: Vec::new(),
}
}
pub fn pipe_name(mut self, pipe_name: PipeName) -> Self {
self.pipe_name = Some(pipe_name);
self
}
pub fn open_mode(mut self, open_mode: NamedPipeOpenMode) -> Self {
self.open_mode = open_mode;
self
}
pub fn pipe_type(mut self, pipe_type: NamedPipeType) -> Self {
self.pipe_type = pipe_type;
self
}
pub fn max_instances(mut self, max_instances: u8) -> Self {
self.max_instances = max_instances;
self
}
pub fn out_buffer_size(mut self, out_buffer_size: u32) -> Self {
self.out_buffer_size = out_buffer_size;
self
}
pub fn in_buffer_size(mut self, in_buffer_size: u32) -> Self {
self.in_buffer_size = in_buffer_size;
self
}
pub fn default_timeout(mut self, default_timeout: Duration) -> Self {
self.default_timeout = default_timeout;
self
}
pub fn security(mut self, security: PipeSecurityOptions) -> Self {
self.security = security;
self
}
pub fn allow_executable(mut self, path: impl Into<PathBuf>) -> Self {
self.allowed_executables.push(path.into());
self
}
pub fn remove_executable(mut self, path: impl Into<PathBuf>) -> Self {
let path = path.into();
self.allowed_executables.retain(|p| {
!p.as_os_str()
.to_string_lossy()
.eq_ignore_ascii_case(&path.as_os_str().to_string_lossy())
});
self
}
pub fn build(self) -> Result<NamedPipeServerConfig> {
let pipe_name = self.pipe_name.ok_or_else(|| {
Error::InvalidParameter(InvalidParameterError::new(
"pipe_name",
"Pipe name must be specified",
))
})?;
if self.max_instances == 0 {
return Err(Error::InvalidParameter(InvalidParameterError::new(
"max_instances",
"max_instances must be at least 1",
)));
}
Ok(NamedPipeServerConfig {
pipe_name,
open_mode: self.open_mode,
pipe_type: self.pipe_type,
max_instances: self.max_instances,
out_buffer_size: self.out_buffer_size,
in_buffer_size: self.in_buffer_size,
default_timeout: self.default_timeout,
security: self.security,
allowed_executables: self.allowed_executables,
})
}
}
impl Default for NamedPipeServerBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct NamedPipeServerConfig {
pipe_name: PipeName,
open_mode: NamedPipeOpenMode,
pipe_type: NamedPipeType,
max_instances: u8,
out_buffer_size: u32,
in_buffer_size: u32,
default_timeout: Duration,
security: PipeSecurityOptions,
allowed_executables: Vec<PathBuf>,
}
impl NamedPipeServerConfig {
pub fn builder() -> NamedPipeServerBuilder {
NamedPipeServerBuilder::new()
}
pub fn create(&self) -> Result<NamedPipeServer> {
let name_wide = to_utf16_nul(self.pipe_name.as_str());
let open_mode = to_server_open_mode(self.open_mode);
let pipe_mode = to_pipe_mode(self.pipe_type);
let max_instances = if self.max_instances == u8::MAX {
PIPE_UNLIMITED_INSTANCES
} else {
self.max_instances as u32
};
let default_timeout_ms = self.default_timeout.as_millis().min(u32::MAX as u128) as u32;
let security_attributes =
NativePipeSecurityAttributes::from_options(&self.security, self.pipe_name.as_str())?;
let raw_handle = unsafe {
CreateNamedPipeW(
PCWSTR(name_wide.as_ptr()),
open_mode,
pipe_mode,
max_instances,
self.out_buffer_size,
self.in_buffer_size,
default_timeout_ms,
security_attributes.as_option_ptr(),
)
};
if raw_handle.is_invalid() {
let code = unsafe { GetLastError().0 as i32 };
return Err(map_pipe_windows_error(
"create",
Some(&self.pipe_name),
code,
));
}
Ok(NamedPipeServer {
endpoint: PipeServerEndpoint::from_raw(
raw_handle,
true,
self.pipe_name.clone(),
self.open_mode,
self.pipe_type,
),
default_timeout: self.default_timeout,
allowed_executables: self.allowed_executables.clone(),
})
}
pub fn pipe_name(&self) -> &PipeName {
&self.pipe_name
}
pub fn open_mode(&self) -> NamedPipeOpenMode {
self.open_mode
}
pub fn pipe_type(&self) -> NamedPipeType {
self.pipe_type
}
pub fn max_instances(&self) -> u8 {
self.max_instances
}
pub fn out_buffer_size(&self) -> u32 {
self.out_buffer_size
}
pub fn in_buffer_size(&self) -> u32 {
self.in_buffer_size
}
pub fn default_timeout(&self) -> Duration {
self.default_timeout
}
pub fn security(&self) -> PipeSecurityOptions {
self.security.clone()
}
}
#[derive(Debug)]
pub struct NamedPipeServer {
endpoint: PipeServerEndpoint,
default_timeout: Duration,
allowed_executables: Vec<PathBuf>,
}
impl NamedPipeServer {
pub fn endpoint(&self) -> &PipeServerEndpoint {
&self.endpoint
}
pub fn default_timeout(&self) -> Duration {
self.default_timeout
}
pub fn allow_executable(&mut self, path: impl Into<PathBuf>) {
self.allowed_executables.push(path.into());
}
pub fn remove_executable(&mut self, path: impl Into<PathBuf>) {
let path = path.into();
self.allowed_executables.retain(|p| {
!p.as_os_str()
.to_string_lossy()
.eq_ignore_ascii_case(&path.as_os_str().to_string_lossy())
});
}
pub fn connect(&self) -> Result<()> {
let result = unsafe { ConnectNamedPipe(self.endpoint.raw_handle(), None) };
if result.is_err() {
let code = unsafe { GetLastError().0 as i32 };
if code != ERROR_PIPE_CONNECTED.0 as i32 {
return Err(map_pipe_windows_error(
"connect",
Some(self.endpoint.pipe_name()),
code,
));
}
}
self.validate_connected_client()?;
Ok(())
}
pub fn connect_with_timeout(&self, timeout: Duration) -> Result<()> {
let wait = Wait::manual_reset(false)?;
self.connect_with_wait_timeout(&wait, timeout)
}
pub fn connect_with_wait(&self, wait: &Wait) -> Result<()> {
self.connect_with_wait_timeout(wait, Duration::MAX)
}
pub fn connect_with_wait_timeout(&self, wait: &Wait, timeout: Duration) -> Result<()> {
let connect_event = Wait::manual_reset(false)?;
let mut overlapped = OVERLAPPED {
hEvent: connect_event.raw_handle(),
..Default::default()
};
let mut connect_code: Option<i32> = None;
let result = unsafe { ConnectNamedPipe(self.endpoint.raw_handle(), Some(&mut overlapped)) };
if result.is_err() {
let code = unsafe { GetLastError().0 as i32 };
connect_code = Some(code);
if code != ERROR_IO_PENDING.0 as i32 && code != ERROR_PIPE_CONNECTED.0 as i32 {
return Err(map_pipe_windows_error(
"connect",
Some(self.endpoint.pipe_name()),
code,
));
}
}
if result.is_ok() || connect_code == Some(ERROR_PIPE_CONNECTED.0 as i32) {
self.validate_connected_client()?;
return Ok(());
}
let handles = [connect_event.raw_handle(), wait.raw_handle()];
let wait_result =
unsafe { WaitForMultipleObjects(&handles, false, duration_to_wait_ms(timeout)) };
if wait_result == WAIT_OBJECT_0 {
let mut transferred = 0u32;
unsafe {
GetOverlappedResult(
self.endpoint.raw_handle(),
&overlapped,
&mut transferred,
false,
)
}
.map_err(|_| {
let code = unsafe { GetLastError().0 as i32 };
map_pipe_windows_error("connect", Some(self.endpoint.pipe_name()), code)
})?;
self.validate_connected_client()?;
return Ok(());
}
if wait_result == windows::Win32::Foundation::WAIT_EVENT(WAIT_OBJECT_0.0 + 1) {
let _ = unsafe { CancelIoEx(self.endpoint.raw_handle(), Some(&overlapped)) };
return Err(Error::Pipe(PipeError::Connect(
PipeConnectError::new(Cow::Owned(self.endpoint.pipe_name().as_str().to_owned()))
.with_context("connect interrupted by wait handle signal")
.with_code(ERROR_OPERATION_ABORTED.0 as i32),
)));
}
if wait_result == WAIT_TIMEOUT {
let _ = unsafe { CancelIoEx(self.endpoint.raw_handle(), Some(&overlapped)) };
return Err(Error::Pipe(PipeError::Timeout(PipeTimeoutError::new(
Cow::Owned(self.endpoint.pipe_name().as_str().to_owned()),
Cow::Borrowed("connect"),
))));
}
let _ = unsafe { CancelIoEx(self.endpoint.raw_handle(), Some(&overlapped)) };
if wait_result == WAIT_FAILED {
let code = unsafe { GetLastError().0 as i32 };
return Err(map_pipe_windows_error(
"connect",
Some(self.endpoint.pipe_name()),
code,
));
}
Err(map_pipe_windows_error(
"connect",
Some(self.endpoint.pipe_name()),
wait_result.0 as i32,
))
}
fn validate_connected_client(&self) -> Result<()> {
if !self.allowed_executables.is_empty()
&& let Err(e) = self.check_client_executable()
{
let _ = self.disconnect();
return Err(e);
}
Ok(())
}
fn check_client_executable(&self) -> Result<()> {
let pipe_name = Cow::Owned(self.endpoint.pipe_name().as_str().to_owned());
let mut pid: u32 = 0;
let ok = unsafe { GetNamedPipeClientProcessId(self.endpoint.raw_handle(), &mut pid) };
if ok.is_err() {
return Err(Error::AccessDenied(AccessDeniedError::with_reason(
pipe_name,
"connect",
"could not determine client process id",
)));
}
let client_path = match Process::open(ProcessId::new(pid)) {
Ok(proc) => match proc.path() {
Ok(p) => p,
Err(_) => {
return Err(Error::AccessDenied(AccessDeniedError::with_reason(
pipe_name,
"connect",
"could not retrieve client executable path",
)));
}
},
Err(_) => {
return Err(Error::AccessDenied(AccessDeniedError::with_reason(
pipe_name,
"connect",
"could not open client process",
)));
}
};
let allowed = self.allowed_executables.iter().any(|allowed| {
allowed
.as_os_str()
.to_string_lossy()
.eq_ignore_ascii_case(&client_path.as_os_str().to_string_lossy())
});
if allowed {
Ok(())
} else {
Err(Error::AccessDenied(AccessDeniedError::with_reason(
pipe_name,
"connect",
Cow::Owned(format!(
"client executable '{}' is not in the allow-list",
client_path.display()
)),
)))
}
}
pub fn disconnect(&self) -> Result<()> {
unsafe { DisconnectNamedPipe(self.endpoint.raw_handle()) }.map_err(|_| {
let code = unsafe { GetLastError().0 as i32 };
map_pipe_windows_error("disconnect", Some(self.endpoint.pipe_name()), code)
})
}
}
fn duration_to_wait_ms(timeout: Duration) -> u32 {
timeout.as_millis().min(u32::MAX as u128) as u32
}
impl io::Read for NamedPipeServer {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut read = 0u32;
unsafe { ReadFile(self.endpoint.raw_handle(), Some(buf), Some(&mut read), None) }
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
Ok(read as usize)
}
}
impl io::Write for NamedPipeServer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut written = 0u32;
unsafe {
WriteFile(
self.endpoint.raw_handle(),
Some(buf),
Some(&mut written),
None,
)
}
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
Ok(written as usize)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn to_server_open_mode(open_mode: NamedPipeOpenMode) -> FILE_FLAGS_AND_ATTRIBUTES {
match open_mode {
NamedPipeOpenMode::Inbound => {
FILE_FLAGS_AND_ATTRIBUTES(PIPE_ACCESS_INBOUND.0 | FILE_FLAG_OVERLAPPED.0)
}
NamedPipeOpenMode::Outbound => {
FILE_FLAGS_AND_ATTRIBUTES(PIPE_ACCESS_OUTBOUND.0 | FILE_FLAG_OVERLAPPED.0)
}
NamedPipeOpenMode::Duplex => {
FILE_FLAGS_AND_ATTRIBUTES(PIPE_ACCESS_DUPLEX.0 | FILE_FLAG_OVERLAPPED.0)
}
}
}
fn to_pipe_mode(pipe_type: NamedPipeType) -> NAMED_PIPE_MODE {
match pipe_type {
NamedPipeType::Byte => NAMED_PIPE_MODE(
PIPE_TYPE_BYTE.0 | PIPE_READMODE_BYTE.0 | PIPE_WAIT.0 | PIPE_REJECT_REMOTE_CLIENTS.0,
),
NamedPipeType::Message => NAMED_PIPE_MODE(
PIPE_TYPE_MESSAGE.0
| PIPE_READMODE_MESSAGE.0
| PIPE_WAIT.0
| PIPE_REJECT_REMOTE_CLIENTS.0,
),
}
}